youtube-knowledge-mcp 2.1.0 → 2.2.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/README.md +105 -34
- package/dist/brain-schemas.d.ts +10 -10
- package/dist/brain-schemas.d.ts.map +1 -1
- package/dist/brain-schemas.js +0 -4
- package/dist/brain-schemas.js.map +1 -1
- package/dist/http/app.d.ts +16 -0
- package/dist/http/app.d.ts.map +1 -0
- package/dist/http/app.js +176 -0
- package/dist/http/app.js.map +1 -0
- package/dist/http/auth.d.ts +38 -0
- package/dist/http/auth.d.ts.map +1 -0
- package/dist/http/auth.js +79 -0
- package/dist/http/auth.js.map +1 -0
- package/dist/http/config.d.ts +41 -0
- package/dist/http/config.d.ts.map +1 -0
- package/dist/http/config.js +65 -0
- package/dist/http/config.js.map +1 -0
- package/dist/http/rate-limiter.d.ts +24 -0
- package/dist/http/rate-limiter.d.ts.map +1 -0
- package/dist/http/rate-limiter.js +55 -0
- package/dist/http/rate-limiter.js.map +1 -0
- package/dist/http/sessions.d.ts +34 -0
- package/dist/http/sessions.d.ts.map +1 -0
- package/dist/http/sessions.js +62 -0
- package/dist/http/sessions.js.map +1 -0
- package/dist/http.d.ts +6 -48
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +35 -252
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -517
- package/dist/index.js.map +1 -1
- package/dist/prompts-brain.d.ts +4 -0
- package/dist/prompts-brain.d.ts.map +1 -0
- package/dist/prompts-brain.js +48 -0
- package/dist/prompts-brain.js.map +1 -0
- package/dist/prompts-research.d.ts +4 -0
- package/dist/prompts-research.d.ts.map +1 -0
- package/dist/prompts-research.js +74 -0
- package/dist/prompts-research.js.map +1 -0
- package/dist/prompts-shared.d.ts +13 -0
- package/dist/prompts-shared.d.ts.map +1 -0
- package/dist/prompts-shared.js +7 -0
- package/dist/prompts-shared.js.map +1 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +5 -100
- package/dist/prompts.js.map +1 -1
- package/dist/registry/brain-tools.d.ts +4 -0
- package/dist/registry/brain-tools.d.ts.map +1 -0
- package/dist/registry/brain-tools.js +104 -0
- package/dist/registry/brain-tools.js.map +1 -0
- package/dist/registry/channel-tools.d.ts +4 -0
- package/dist/registry/channel-tools.d.ts.map +1 -0
- package/dist/registry/channel-tools.js +53 -0
- package/dist/registry/channel-tools.js.map +1 -0
- package/dist/registry/health-tools.d.ts +4 -0
- package/dist/registry/health-tools.d.ts.map +1 -0
- package/dist/registry/health-tools.js +21 -0
- package/dist/registry/health-tools.js.map +1 -0
- package/dist/registry/index.d.ts +8 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +27 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/library-tools.d.ts +4 -0
- package/dist/registry/library-tools.d.ts.map +1 -0
- package/dist/registry/library-tools.js +116 -0
- package/dist/registry/library-tools.js.map +1 -0
- package/dist/registry/media-tools.d.ts +4 -0
- package/dist/registry/media-tools.d.ts.map +1 -0
- package/dist/registry/media-tools.js +100 -0
- package/dist/registry/media-tools.js.map +1 -0
- package/dist/registry/thumbnail-tools.d.ts +7 -0
- package/dist/registry/thumbnail-tools.d.ts.map +1 -0
- package/dist/registry/thumbnail-tools.js +72 -0
- package/dist/registry/thumbnail-tools.js.map +1 -0
- package/dist/registry/transcript-tools.d.ts +4 -0
- package/dist/registry/transcript-tools.d.ts.map +1 -0
- package/dist/registry/transcript-tools.js +68 -0
- package/dist/registry/transcript-tools.js.map +1 -0
- package/dist/registry/types.d.ts +41 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +17 -0
- package/dist/registry/types.js.map +1 -0
- package/dist/registry/video-tools.d.ts +4 -0
- package/dist/registry/video-tools.d.ts.map +1 -0
- package/dist/registry/video-tools.js +101 -0
- package/dist/registry/video-tools.js.map +1 -0
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +13 -10
- package/dist/resources.js.map +1 -1
- package/dist/schemas.d.ts +11 -2
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +5 -0
- package/dist/schemas.js.map +1 -1
- package/dist/thumbnail-resource.d.ts +8 -0
- package/dist/thumbnail-resource.d.ts.map +1 -0
- package/dist/thumbnail-resource.js +38 -0
- package/dist/thumbnail-resource.js.map +1 -0
- package/dist/thumbnail-schemas.d.ts +512 -0
- package/dist/thumbnail-schemas.d.ts.map +1 -0
- package/dist/thumbnail-schemas.js +95 -0
- package/dist/thumbnail-schemas.js.map +1 -0
- package/dist/tools/batch.js +1 -1
- package/dist/tools/batch.js.map +1 -1
- package/dist/tools/build-brain.js +1 -1
- package/dist/tools/build-brain.js.map +1 -1
- package/dist/tools/check-health.d.ts +2 -0
- package/dist/tools/check-health.d.ts.map +1 -1
- package/dist/tools/check-health.js +9 -0
- package/dist/tools/check-health.js.map +1 -1
- package/dist/tools/clips.d.ts +9 -41
- package/dist/tools/clips.d.ts.map +1 -1
- package/dist/tools/clips.js +52 -128
- package/dist/tools/clips.js.map +1 -1
- package/dist/tools/fetch-channel-thumbnails.d.ts +179 -0
- package/dist/tools/fetch-channel-thumbnails.d.ts.map +1 -0
- package/dist/tools/fetch-channel-thumbnails.js +123 -0
- package/dist/tools/fetch-channel-thumbnails.js.map +1 -0
- package/dist/tools/fetch-videos.d.ts +3 -0
- package/dist/tools/fetch-videos.d.ts.map +1 -1
- package/dist/tools/fetch-videos.js +3 -0
- package/dist/tools/fetch-videos.js.map +1 -1
- package/dist/tools/frames-subtitles.d.ts +44 -0
- package/dist/tools/frames-subtitles.d.ts.map +1 -0
- package/dist/tools/frames-subtitles.js +104 -0
- package/dist/tools/frames-subtitles.js.map +1 -0
- package/dist/tools/get-channel-info.d.ts +2 -0
- package/dist/tools/get-channel-info.d.ts.map +1 -1
- package/dist/tools/get-channel-info.js +8 -1
- package/dist/tools/get-channel-info.js.map +1 -1
- package/dist/tools/get-playlist-info.d.ts.map +1 -1
- package/dist/tools/get-playlist-info.js +1 -0
- package/dist/tools/get-playlist-info.js.map +1 -1
- package/dist/tools/get-thumbnail.d.ts +38 -0
- package/dist/tools/get-thumbnail.d.ts.map +1 -0
- package/dist/tools/get-thumbnail.js +152 -0
- package/dist/tools/get-thumbnail.js.map +1 -0
- package/dist/tools/get-transcript.d.ts.map +1 -1
- package/dist/tools/get-transcript.js +2 -14
- package/dist/tools/get-transcript.js.map +1 -1
- package/dist/tools/library.d.ts.map +1 -1
- package/dist/tools/library.js +13 -3
- package/dist/tools/library.js.map +1 -1
- package/dist/tools/list-channel-thumbnails.d.ts +235 -0
- package/dist/tools/list-channel-thumbnails.d.ts.map +1 -0
- package/dist/tools/list-channel-thumbnails.js +79 -0
- package/dist/tools/list-channel-thumbnails.js.map +1 -0
- package/dist/tools/save-to-library.d.ts.map +1 -1
- package/dist/tools/save-to-library.js +5 -1
- package/dist/tools/save-to-library.js.map +1 -1
- package/dist/tools/search-channels.d.ts +6 -0
- package/dist/tools/search-channels.d.ts.map +1 -1
- package/dist/tools/search-transcript.d.ts.map +1 -1
- package/dist/tools/search-transcript.js +6 -13
- package/dist/tools/search-transcript.js.map +1 -1
- package/dist/tools/search-videos.d.ts +3 -0
- package/dist/tools/search-videos.d.ts.map +1 -1
- package/dist/utils/batches.d.ts +15 -0
- package/dist/utils/batches.d.ts.map +1 -0
- package/dist/utils/batches.js +31 -0
- package/dist/utils/batches.js.map +1 -0
- package/dist/utils/brain-build.d.ts.map +1 -1
- package/dist/utils/brain-build.js +1 -12
- package/dist/utils/brain-build.js.map +1 -1
- package/dist/utils/brain-index.d.ts.map +1 -1
- package/dist/utils/brain-index.js +3 -2
- package/dist/utils/brain-index.js.map +1 -1
- package/dist/utils/brain-lock.d.ts +2 -18
- package/dist/utils/brain-lock.d.ts.map +1 -1
- package/dist/utils/brain-lock.js +4 -80
- package/dist/utils/brain-lock.js.map +1 -1
- package/dist/utils/brain-lookup.d.ts +8 -0
- package/dist/utils/brain-lookup.d.ts.map +1 -1
- package/dist/utils/brain-lookup.js +5 -31
- package/dist/utils/brain-lookup.js.map +1 -1
- package/dist/utils/brain-storage.d.ts.map +1 -1
- package/dist/utils/brain-storage.js +5 -5
- package/dist/utils/brain-storage.js.map +1 -1
- package/dist/utils/caption-probe.d.ts +7 -0
- package/dist/utils/caption-probe.d.ts.map +1 -0
- package/dist/utils/caption-probe.js +36 -0
- package/dist/utils/caption-probe.js.map +1 -0
- package/dist/utils/channel-images.d.ts +16 -0
- package/dist/utils/channel-images.d.ts.map +1 -0
- package/dist/utils/channel-images.js +35 -0
- package/dist/utils/channel-images.js.map +1 -0
- package/dist/utils/channel-lookup.d.ts +5 -0
- package/dist/utils/channel-lookup.d.ts.map +1 -0
- package/dist/utils/channel-lookup.js +37 -0
- package/dist/utils/channel-lookup.js.map +1 -0
- package/dist/utils/chapters.d.ts +11 -0
- package/dist/utils/chapters.d.ts.map +1 -0
- package/dist/utils/chapters.js +24 -0
- package/dist/utils/chapters.js.map +1 -0
- package/dist/utils/clips.d.ts.map +1 -1
- package/dist/utils/clips.js +18 -26
- package/dist/utils/clips.js.map +1 -1
- package/dist/utils/env.d.ts +26 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +72 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/errors.d.ts +1 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +20 -2
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/file-lock.d.ts +34 -0
- package/dist/utils/file-lock.d.ts.map +1 -0
- package/dist/utils/file-lock.js +91 -0
- package/dist/utils/file-lock.js.map +1 -0
- package/dist/utils/flat-listing.d.ts +89 -0
- package/dist/utils/flat-listing.d.ts.map +1 -0
- package/dist/utils/flat-listing.js +68 -0
- package/dist/utils/flat-listing.js.map +1 -0
- package/dist/utils/format.d.ts +1 -3
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +1 -5
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/guard.d.ts +21 -0
- package/dist/utils/guard.d.ts.map +1 -0
- package/dist/utils/guard.js +105 -0
- package/dist/utils/guard.js.map +1 -0
- package/dist/utils/image-dimensions.d.ts +19 -0
- package/dist/utils/image-dimensions.d.ts.map +1 -0
- package/dist/utils/image-dimensions.js +93 -0
- package/dist/utils/image-dimensions.js.map +1 -0
- package/dist/utils/image-fetch.d.ts +31 -0
- package/dist/utils/image-fetch.d.ts.map +1 -0
- package/dist/utils/image-fetch.js +141 -0
- package/dist/utils/image-fetch.js.map +1 -0
- package/dist/utils/json-file.d.ts +14 -5
- package/dist/utils/json-file.d.ts.map +1 -1
- package/dist/utils/json-file.js +24 -9
- package/dist/utils/json-file.js.map +1 -1
- package/dist/utils/paths.d.ts +10 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +14 -0
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/pattern.d.ts +30 -0
- package/dist/utils/pattern.d.ts.map +1 -0
- package/dist/utils/pattern.js +130 -0
- package/dist/utils/pattern.js.map +1 -0
- package/dist/utils/search-index.d.ts.map +1 -1
- package/dist/utils/search-index.js +9 -4
- package/dist/utils/search-index.js.map +1 -1
- package/dist/utils/storage.d.ts.map +1 -1
- package/dist/utils/storage.js +14 -8
- package/dist/utils/storage.js.map +1 -1
- package/dist/utils/subtitles.d.ts +7 -0
- package/dist/utils/subtitles.d.ts.map +1 -0
- package/dist/utils/subtitles.js +30 -0
- package/dist/utils/subtitles.js.map +1 -0
- package/dist/utils/thumbnail-entry.d.ts +19 -0
- package/dist/utils/thumbnail-entry.d.ts.map +1 -0
- package/dist/utils/thumbnail-entry.js +119 -0
- package/dist/utils/thumbnail-entry.js.map +1 -0
- package/dist/utils/thumbnail-fetch.d.ts +37 -0
- package/dist/utils/thumbnail-fetch.d.ts.map +1 -0
- package/dist/utils/thumbnail-fetch.js +89 -0
- package/dist/utils/thumbnail-fetch.js.map +1 -0
- package/dist/utils/thumbnail-ladder.d.ts +44 -0
- package/dist/utils/thumbnail-ladder.d.ts.map +1 -0
- package/dist/utils/thumbnail-ladder.js +68 -0
- package/dist/utils/thumbnail-ladder.js.map +1 -0
- package/dist/utils/thumbnail-listing.d.ts +16 -0
- package/dist/utils/thumbnail-listing.d.ts.map +1 -0
- package/dist/utils/thumbnail-listing.js +26 -0
- package/dist/utils/thumbnail-listing.js.map +1 -0
- package/dist/utils/thumbnail-paths.d.ts +20 -0
- package/dist/utils/thumbnail-paths.d.ts.map +1 -0
- package/dist/utils/thumbnail-paths.js +35 -0
- package/dist/utils/thumbnail-paths.js.map +1 -0
- package/dist/utils/thumbnail-store.d.ts +19 -0
- package/dist/utils/thumbnail-store.d.ts.map +1 -0
- package/dist/utils/thumbnail-store.js +125 -0
- package/dist/utils/thumbnail-store.js.map +1 -0
- package/dist/utils/transcript-cache.d.ts +20 -0
- package/dist/utils/transcript-cache.d.ts.map +1 -0
- package/dist/utils/transcript-cache.js +127 -0
- package/dist/utils/transcript-cache.js.map +1 -0
- package/dist/utils/transcript-search.d.ts +20 -0
- package/dist/utils/transcript-search.d.ts.map +1 -0
- package/dist/utils/transcript-search.js +35 -0
- package/dist/utils/transcript-search.js.map +1 -0
- package/dist/utils/transcript.d.ts +10 -31
- package/dist/utils/transcript.d.ts.map +1 -1
- package/dist/utils/transcript.js +2 -64
- package/dist/utils/transcript.js.map +1 -1
- package/dist/utils/validate-youtube.d.ts +35 -0
- package/dist/utils/validate-youtube.d.ts.map +1 -0
- package/dist/utils/validate-youtube.js +87 -0
- package/dist/utils/validate-youtube.js.map +1 -0
- package/dist/utils/validate.d.ts +11 -4
- package/dist/utils/validate.d.ts.map +1 -1
- package/dist/utils/validate.js +43 -36
- package/dist/utils/validate.js.map +1 -1
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +19 -0
- package/dist/utils/version.js.map +1 -0
- package/dist/utils/youtube-channel.d.ts +44 -0
- package/dist/utils/youtube-channel.d.ts.map +1 -0
- package/dist/utils/youtube-channel.js +61 -0
- package/dist/utils/youtube-channel.js.map +1 -0
- package/dist/utils/youtube-download.d.ts +27 -0
- package/dist/utils/youtube-download.d.ts.map +1 -0
- package/dist/utils/youtube-download.js +118 -0
- package/dist/utils/youtube-download.js.map +1 -0
- package/dist/utils/youtube-schemas.d.ts +394 -0
- package/dist/utils/youtube-schemas.d.ts.map +1 -0
- package/dist/utils/youtube-schemas.js +118 -0
- package/dist/utils/youtube-schemas.js.map +1 -0
- package/dist/utils/youtube-search.d.ts +14 -0
- package/dist/utils/youtube-search.d.ts.map +1 -0
- package/dist/utils/youtube-search.js +39 -0
- package/dist/utils/youtube-search.js.map +1 -0
- package/dist/utils/youtube-url.d.ts +25 -0
- package/dist/utils/youtube-url.d.ts.map +1 -0
- package/dist/utils/youtube-url.js +75 -0
- package/dist/utils/youtube-url.js.map +1 -0
- package/dist/utils/youtube-video.d.ts +47 -0
- package/dist/utils/youtube-video.d.ts.map +1 -0
- package/dist/utils/youtube-video.js +98 -0
- package/dist/utils/youtube-video.js.map +1 -0
- package/dist/utils/youtube.d.ts +10 -122
- package/dist/utils/youtube.d.ts.map +1 -1
- package/dist/utils/youtube.js +11 -486
- package/dist/utils/youtube.js.map +1 -1
- package/dist/utils/ytdlp-env.d.ts +33 -0
- package/dist/utils/ytdlp-env.d.ts.map +1 -0
- package/dist/utils/ytdlp-env.js +121 -0
- package/dist/utils/ytdlp-env.js.map +1 -0
- package/dist/utils/ytdlp-limiter.d.ts +24 -0
- package/dist/utils/ytdlp-limiter.d.ts.map +1 -0
- package/dist/utils/ytdlp-limiter.js +69 -0
- package/dist/utils/ytdlp-limiter.js.map +1 -0
- package/dist/utils/ytdlp-parse.d.ts +13 -0
- package/dist/utils/ytdlp-parse.d.ts.map +1 -0
- package/dist/utils/ytdlp-parse.js +48 -0
- package/dist/utils/ytdlp-parse.js.map +1 -0
- package/dist/utils/ytdlp.d.ts +13 -18
- package/dist/utils/ytdlp.d.ts.map +1 -1
- package/dist/utils/ytdlp.js +21 -110
- package/dist/utils/ytdlp.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
2
|
+
/** Bearer tokens, client identity, session ids and the JSON-RPC error envelope. */
|
|
3
|
+
/**
|
|
4
|
+
* Compare a presented token against the configured one in constant time, so the
|
|
5
|
+
* endpoint does not leak the token's prefix through response timing.
|
|
6
|
+
*/
|
|
7
|
+
export function tokenMatches(presented, expected) {
|
|
8
|
+
if (presented === undefined)
|
|
9
|
+
return false;
|
|
10
|
+
const a = Buffer.from(presented);
|
|
11
|
+
const b = Buffer.from(expected);
|
|
12
|
+
// timingSafeEqual throws on length mismatch, which would itself be a leak;
|
|
13
|
+
// hash-free equalisation keeps the comparison constant-time for equal lengths
|
|
14
|
+
// and constant-result for unequal ones.
|
|
15
|
+
if (a.length !== b.length) {
|
|
16
|
+
timingSafeEqual(a, a);
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return timingSafeEqual(a, b);
|
|
20
|
+
}
|
|
21
|
+
export function bearerToken(header) {
|
|
22
|
+
if (!header)
|
|
23
|
+
return undefined;
|
|
24
|
+
const match = /^Bearer\s+(.+)$/i.exec(header.trim());
|
|
25
|
+
return match?.[1];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The key a client is rate-limited under.
|
|
29
|
+
*
|
|
30
|
+
* `req.ip` is what Express derives after applying the `trust proxy` setting:
|
|
31
|
+
* the socket address by default, and the forwarded client only when the
|
|
32
|
+
* operator has said which proxies to believe. Reading `X-Forwarded-For`
|
|
33
|
+
* directly, as this once did, let any caller mint a fresh quota per request.
|
|
34
|
+
*/
|
|
35
|
+
export function clientKey(req) {
|
|
36
|
+
return req.ip ?? req.socket.remoteAddress ?? 'unknown';
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Node parses a repeated header into an array, so this field is genuinely
|
|
40
|
+
* `string | string[] | undefined`. Asserting it was always a string meant a
|
|
41
|
+
* request that sent `Mcp-Session-Id` twice reached `sessions.get()` holding an
|
|
42
|
+
* array, missed every session, and was reported as an invalid session ID.
|
|
43
|
+
* Ambiguous is treated as absent: two session IDs identify no session.
|
|
44
|
+
*/
|
|
45
|
+
export function sessionIdOf(req) {
|
|
46
|
+
const header = req.headers['mcp-session-id'];
|
|
47
|
+
return typeof header === 'string' ? header : undefined;
|
|
48
|
+
}
|
|
49
|
+
export function jsonRpcError(res, status, code, message) {
|
|
50
|
+
if (res.headersSent)
|
|
51
|
+
return;
|
|
52
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
53
|
+
res.end(JSON.stringify({ jsonrpc: '2.0', error: { code, message }, id: null }));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The base URL this server publishes in OAuth metadata.
|
|
57
|
+
*
|
|
58
|
+
* Configuration first; the request's own view of itself only when a proxy is
|
|
59
|
+
* trusted to have set it. Reflecting `Host` unconditionally let anyone who
|
|
60
|
+
* could reach the endpoint choose the address other clients were told to use.
|
|
61
|
+
*/
|
|
62
|
+
export function publicBaseUrl(config, req, port) {
|
|
63
|
+
if (config.publicUrl !== undefined)
|
|
64
|
+
return config.publicUrl;
|
|
65
|
+
if (config.trustProxy !== false)
|
|
66
|
+
return `${req.protocol}://${req.get('host') ?? ''}`;
|
|
67
|
+
return `http://${config.bindHost}:${port}`;
|
|
68
|
+
}
|
|
69
|
+
/** Headers every response carries; none of them cost anything for an API. */
|
|
70
|
+
export function securityHeaders() {
|
|
71
|
+
return (_req, res, next) => {
|
|
72
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
73
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
74
|
+
res.setHeader('Referrer-Policy', 'no-referrer');
|
|
75
|
+
res.setHeader('X-Frame-Options', 'DENY');
|
|
76
|
+
next();
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/http/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAI9C,mFAAmF;AAEnF;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,SAA6B,EAAE,QAAgB;IAC1E,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAE1C,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACjC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChC,2EAA2E;IAC3E,8EAA8E;IAC9E,wCAAwC;IACxC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;QAC1B,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAA0B;IACpD,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACrD,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,GAAY;IACpC,OAAO,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS,CAAC;AACzD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,GAAY;IACtC,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC7C,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAa,EAAE,MAAc,EAAE,IAAY,EAAE,OAAe;IACvF,IAAI,GAAG,CAAC,WAAW;QAAE,OAAO;IAC5B,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAC9D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAClF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,MAAkB,EAAE,GAAY,EAAE,IAAY;IAC1E,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,SAAS,CAAC;IAC5D,IAAI,MAAM,CAAC,UAAU,KAAK,KAAK;QAAE,OAAO,GAAG,GAAG,CAAC,QAAQ,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;IACrF,OAAO,UAAU,MAAM,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;AAC7C,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,eAAe;IAC7B,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACzB,GAAG,CAAC,SAAS,CAAC,wBAAwB,EAAE,SAAS,CAAC,CAAC;QACnD,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAC3C,GAAG,CAAC,SAAS,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;QAChD,GAAG,CAAC,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;QACzC,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Deployment configuration for the Streamable HTTP transport, read from the environment. */
|
|
2
|
+
export interface HttpConfig {
|
|
3
|
+
port: number;
|
|
4
|
+
/** Interface to bind. Loopback by default; containers set 0.0.0.0 explicitly. */
|
|
5
|
+
bindHost: string;
|
|
6
|
+
/** When set, every request must present this as a bearer token. */
|
|
7
|
+
authToken?: string;
|
|
8
|
+
/** Host header allowlist. Empty means "accept any", which is only safe behind a proxy. */
|
|
9
|
+
allowedHosts: string[];
|
|
10
|
+
/** Origin header allowlist for browser callers. */
|
|
11
|
+
allowedOrigins: string[];
|
|
12
|
+
rateLimit: number;
|
|
13
|
+
rateWindowMs: number;
|
|
14
|
+
/** Sessions idle longer than this are closed, so the map cannot grow forever. */
|
|
15
|
+
sessionIdleMs: number;
|
|
16
|
+
maxSessions: number;
|
|
17
|
+
/**
|
|
18
|
+
* Whether `X-Forwarded-*` headers are believed: `false`, `true`, or the number
|
|
19
|
+
* of proxy hops in front of the server. Off by default, because a client can
|
|
20
|
+
* write those headers itself.
|
|
21
|
+
*/
|
|
22
|
+
trustProxy: boolean | number;
|
|
23
|
+
/** The URL clients reach this server at, for the OAuth metadata it publishes. */
|
|
24
|
+
publicUrl?: string;
|
|
25
|
+
/** Largest request body accepted, in bytes. */
|
|
26
|
+
maxBodyBytes: number;
|
|
27
|
+
/** Explicit consent to listen on a non-loopback interface without a token. */
|
|
28
|
+
allowUnauthenticated: boolean;
|
|
29
|
+
}
|
|
30
|
+
export declare const DEFAULT_MAX_BODY_BYTES: number;
|
|
31
|
+
export declare function readHttpConfig(): HttpConfig;
|
|
32
|
+
export declare function isLoopback(host: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Refuse to expose an unauthenticated server to a network.
|
|
35
|
+
*
|
|
36
|
+
* Binding beyond loopback with no token used to be the default and was merely
|
|
37
|
+
* logged. Now it is a startup error unless the operator has said, in so many
|
|
38
|
+
* words, that they mean it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function assertSafeToStart(config: HttpConfig): void;
|
|
41
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/http/config.ts"],"names":[],"mappings":"AAEA,6FAA6F;AAE7F,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,mDAAmD;IACnD,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,UAAU,EAAE,OAAO,GAAG,MAAM,CAAC;IAC7B,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,YAAY,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,oBAAoB,EAAE,OAAO,CAAC;CAC/B;AAED,eAAO,MAAM,sBAAsB,QAAc,CAAC;AAsBlD,wBAAgB,cAAc,IAAI,UAAU,CAkB3C;AAID,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAY1D"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { envBool, envInt, envList, envString } from '../utils/env.js';
|
|
2
|
+
export const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;
|
|
3
|
+
function readTrustProxy() {
|
|
4
|
+
const raw = envString('MCP_TRUST_PROXY');
|
|
5
|
+
if (raw === undefined)
|
|
6
|
+
return false;
|
|
7
|
+
if (/^\d+$/.test(raw))
|
|
8
|
+
return Number(raw);
|
|
9
|
+
return envBool('MCP_TRUST_PROXY', false);
|
|
10
|
+
}
|
|
11
|
+
function readPublicUrl() {
|
|
12
|
+
const raw = envString('MCP_PUBLIC_URL');
|
|
13
|
+
if (raw === undefined)
|
|
14
|
+
return undefined;
|
|
15
|
+
try {
|
|
16
|
+
const url = new URL(raw);
|
|
17
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:')
|
|
18
|
+
throw new Error('scheme');
|
|
19
|
+
return url.toString().replace(/\/+$/, '');
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
console.error('Ignoring MCP_PUBLIC_URL: not an http(s) URL.');
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function readHttpConfig() {
|
|
27
|
+
const publicUrl = readPublicUrl();
|
|
28
|
+
return {
|
|
29
|
+
port: envInt('PORT', 3000, { min: 1, max: 65_535 }),
|
|
30
|
+
bindHost: envString('MCP_BIND_HOST') ?? '127.0.0.1',
|
|
31
|
+
// An empty MCP_AUTH_TOKEN must mean "no auth", not "the empty token".
|
|
32
|
+
authToken: envString('MCP_AUTH_TOKEN'),
|
|
33
|
+
allowedHosts: envList('MCP_ALLOWED_HOSTS'),
|
|
34
|
+
allowedOrigins: envList('MCP_ALLOWED_ORIGINS'),
|
|
35
|
+
rateLimit: envInt('MCP_RATE_LIMIT', 60, { min: 1 }),
|
|
36
|
+
rateWindowMs: envInt('MCP_RATE_WINDOW_MS', 60_000, { min: 1 }),
|
|
37
|
+
sessionIdleMs: envInt('MCP_SESSION_IDLE_MS', 30 * 60_000, { min: 1 }),
|
|
38
|
+
maxSessions: envInt('MCP_MAX_SESSIONS', 1000, { min: 1 }),
|
|
39
|
+
trustProxy: readTrustProxy(),
|
|
40
|
+
...(publicUrl === undefined ? {} : { publicUrl }),
|
|
41
|
+
maxBodyBytes: envInt('MCP_MAX_BODY_BYTES', DEFAULT_MAX_BODY_BYTES, { min: 1024 }),
|
|
42
|
+
allowUnauthenticated: envBool('MCP_ALLOW_UNAUTHENTICATED', false),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '::ffff:127.0.0.1']);
|
|
46
|
+
export function isLoopback(host) {
|
|
47
|
+
return LOOPBACK_HOSTS.has(host.toLowerCase());
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Refuse to expose an unauthenticated server to a network.
|
|
51
|
+
*
|
|
52
|
+
* Binding beyond loopback with no token used to be the default and was merely
|
|
53
|
+
* logged. Now it is a startup error unless the operator has said, in so many
|
|
54
|
+
* words, that they mean it.
|
|
55
|
+
*/
|
|
56
|
+
export function assertSafeToStart(config) {
|
|
57
|
+
if (isLoopback(config.bindHost) ||
|
|
58
|
+
config.authToken !== undefined ||
|
|
59
|
+
config.allowUnauthenticated) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
throw new Error(`Refusing to listen on ${config.bindHost} without authentication. ` +
|
|
63
|
+
'Set MCP_AUTH_TOKEN, or set MCP_ALLOW_UNAUTHENTICATED=true to run open on purpose.');
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/http/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixed-window counter, per client.
|
|
3
|
+
*
|
|
4
|
+
* The map is capped as well as swept. A client that can vary its key at will
|
|
5
|
+
* — which is what an unvalidated forwarding header allowed — could otherwise
|
|
6
|
+
* grow it without limit inside a single window.
|
|
7
|
+
*/
|
|
8
|
+
export declare class RateLimiter {
|
|
9
|
+
private readonly limit;
|
|
10
|
+
private readonly windowMs;
|
|
11
|
+
private readonly maxKeys;
|
|
12
|
+
private readonly hits;
|
|
13
|
+
constructor(limit: number, windowMs: number, maxKeys?: number);
|
|
14
|
+
check(key: string, now?: number): {
|
|
15
|
+
allowed: boolean;
|
|
16
|
+
remaining: number;
|
|
17
|
+
retryAfter: number;
|
|
18
|
+
};
|
|
19
|
+
/** Expired entries go first; failing that, the oldest live one (Map insertion order). */
|
|
20
|
+
private makeRoom;
|
|
21
|
+
sweep(now?: number): void;
|
|
22
|
+
get size(): number;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/http/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,qBAAa,WAAW;IAIpB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyD;gBAG3D,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,SAAS;IAGnC,KAAK,CACH,GAAG,EAAE,MAAM,EACX,GAAG,SAAa,GACf;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;IAkB9D,yFAAyF;IACzF,OAAO,CAAC,QAAQ;IAQhB,KAAK,CAAC,GAAG,SAAa,GAAG,IAAI;IAM7B,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixed-window counter, per client.
|
|
3
|
+
*
|
|
4
|
+
* The map is capped as well as swept. A client that can vary its key at will
|
|
5
|
+
* — which is what an unvalidated forwarding header allowed — could otherwise
|
|
6
|
+
* grow it without limit inside a single window.
|
|
7
|
+
*/
|
|
8
|
+
export class RateLimiter {
|
|
9
|
+
limit;
|
|
10
|
+
windowMs;
|
|
11
|
+
maxKeys;
|
|
12
|
+
hits = new Map();
|
|
13
|
+
constructor(limit, windowMs, maxKeys = 10_000) {
|
|
14
|
+
this.limit = limit;
|
|
15
|
+
this.windowMs = windowMs;
|
|
16
|
+
this.maxKeys = maxKeys;
|
|
17
|
+
}
|
|
18
|
+
check(key, now = Date.now()) {
|
|
19
|
+
const entry = this.hits.get(key);
|
|
20
|
+
if (!entry || now > entry.resetAt) {
|
|
21
|
+
if (!entry)
|
|
22
|
+
this.makeRoom(now);
|
|
23
|
+
this.hits.set(key, { count: 1, resetAt: now + this.windowMs });
|
|
24
|
+
return { allowed: true, remaining: this.limit - 1, retryAfter: 0 };
|
|
25
|
+
}
|
|
26
|
+
entry.count++;
|
|
27
|
+
const allowed = entry.count <= this.limit;
|
|
28
|
+
return {
|
|
29
|
+
allowed,
|
|
30
|
+
remaining: Math.max(0, this.limit - entry.count),
|
|
31
|
+
retryAfter: allowed ? 0 : Math.ceil((entry.resetAt - now) / 1000),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Expired entries go first; failing that, the oldest live one (Map insertion order). */
|
|
35
|
+
makeRoom(now) {
|
|
36
|
+
if (this.hits.size < this.maxKeys)
|
|
37
|
+
return;
|
|
38
|
+
this.sweep(now);
|
|
39
|
+
if (this.hits.size < this.maxKeys)
|
|
40
|
+
return;
|
|
41
|
+
const oldest = this.hits.keys().next();
|
|
42
|
+
if (!oldest.done)
|
|
43
|
+
this.hits.delete(oldest.value);
|
|
44
|
+
}
|
|
45
|
+
sweep(now = Date.now()) {
|
|
46
|
+
for (const [key, entry] of this.hits) {
|
|
47
|
+
if (now > entry.resetAt)
|
|
48
|
+
this.hits.delete(key);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
get size() {
|
|
52
|
+
return this.hits.size;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/http/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,OAAO,WAAW;IAIH;IACA;IACA;IALF,IAAI,GAAG,IAAI,GAAG,EAA8C,CAAC;IAE9E,YACmB,KAAa,EACb,QAAgB,EAChB,UAAU,MAAM;QAFhB,UAAK,GAAL,KAAK,CAAQ;QACb,aAAQ,GAAR,QAAQ,CAAQ;QAChB,YAAO,GAAP,OAAO,CAAS;IAChC,CAAC;IAEJ,KAAK,CACH,GAAW,EACX,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QAEhB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEjC,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;YAClC,IAAI,CAAC,KAAK;gBAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC/D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;QACrE,CAAC;QAED,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC;QAC1C,OAAO;YACL,OAAO;YACP,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;YAChD,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC;SAClE,CAAC;IACJ,CAAC;IAED,yFAAyF;IACjF,QAAQ,CAAC,GAAW;QAC1B,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAChB,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;QACvC,IAAI,CAAC,MAAM,CAAC,IAAI;YAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACpB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACrC,IAAI,GAAG,GAAG,KAAK,CAAC,OAAO;gBAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IACxB,CAAC;CACF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
2
|
+
/** Live MCP sessions, keyed by the id the transport issued. */
|
|
3
|
+
export interface Session {
|
|
4
|
+
transport: StreamableHTTPServerTransport;
|
|
5
|
+
lastSeen: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Capacity is reserved synchronously, before the awaits that create a session.
|
|
9
|
+
*
|
|
10
|
+
* Checking `size` and then awaiting let every concurrent initialize pass the
|
|
11
|
+
* check together and overshoot the cap. A reservation counts from the moment
|
|
12
|
+
* the decision is made until the session is committed or abandoned.
|
|
13
|
+
*/
|
|
14
|
+
export declare class SessionStore {
|
|
15
|
+
private readonly maxSessions;
|
|
16
|
+
private readonly sessions;
|
|
17
|
+
private reserved;
|
|
18
|
+
constructor(maxSessions: number);
|
|
19
|
+
/** Claims a slot, or reports that none is free. Pending reservations count. */
|
|
20
|
+
reserve(): boolean;
|
|
21
|
+
/** Turns a reservation into a session. */
|
|
22
|
+
commit(id: string, transport: StreamableHTTPServerTransport): void;
|
|
23
|
+
/** Gives a reservation back when the session never came to be. */
|
|
24
|
+
release(): void;
|
|
25
|
+
get(id: string): Session | undefined;
|
|
26
|
+
touch(id: string): void;
|
|
27
|
+
delete(id: string): void;
|
|
28
|
+
/** Closes and forgets every session idle longer than `idleMs`. */
|
|
29
|
+
sweep(now: number, idleMs: number): void;
|
|
30
|
+
closeAll(): Promise<void>;
|
|
31
|
+
/** Committed sessions only; reservations are transient. */
|
|
32
|
+
get size(): number;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/http/sessions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAExG,+DAA+D;AAE/D,MAAM,WAAW,OAAO;IACtB,SAAS,EAAE,6BAA6B,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,qBAAa,YAAY;IAIX,OAAO,CAAC,QAAQ,CAAC,WAAW;IAHxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8B;IACvD,OAAO,CAAC,QAAQ,CAAK;gBAEQ,WAAW,EAAE,MAAM;IAEhD,+EAA+E;IAC/E,OAAO,IAAI,OAAO;IAMlB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,6BAA6B,GAAG,IAAI;IAKlE,kEAAkE;IAClE,OAAO,IAAI,IAAI;IAIf,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAIpC,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAKvB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAIxB,kEAAkE;IAClE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IASlC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAO/B,2DAA2D;IAC3D,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capacity is reserved synchronously, before the awaits that create a session.
|
|
3
|
+
*
|
|
4
|
+
* Checking `size` and then awaiting let every concurrent initialize pass the
|
|
5
|
+
* check together and overshoot the cap. A reservation counts from the moment
|
|
6
|
+
* the decision is made until the session is committed or abandoned.
|
|
7
|
+
*/
|
|
8
|
+
export class SessionStore {
|
|
9
|
+
maxSessions;
|
|
10
|
+
sessions = new Map();
|
|
11
|
+
reserved = 0;
|
|
12
|
+
constructor(maxSessions) {
|
|
13
|
+
this.maxSessions = maxSessions;
|
|
14
|
+
}
|
|
15
|
+
/** Claims a slot, or reports that none is free. Pending reservations count. */
|
|
16
|
+
reserve() {
|
|
17
|
+
if (this.sessions.size + this.reserved >= this.maxSessions)
|
|
18
|
+
return false;
|
|
19
|
+
this.reserved++;
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
/** Turns a reservation into a session. */
|
|
23
|
+
commit(id, transport) {
|
|
24
|
+
this.reserved = Math.max(0, this.reserved - 1);
|
|
25
|
+
this.sessions.set(id, { transport, lastSeen: Date.now() });
|
|
26
|
+
}
|
|
27
|
+
/** Gives a reservation back when the session never came to be. */
|
|
28
|
+
release() {
|
|
29
|
+
this.reserved = Math.max(0, this.reserved - 1);
|
|
30
|
+
}
|
|
31
|
+
get(id) {
|
|
32
|
+
return this.sessions.get(id);
|
|
33
|
+
}
|
|
34
|
+
touch(id) {
|
|
35
|
+
const session = this.sessions.get(id);
|
|
36
|
+
if (session)
|
|
37
|
+
session.lastSeen = Date.now();
|
|
38
|
+
}
|
|
39
|
+
delete(id) {
|
|
40
|
+
this.sessions.delete(id);
|
|
41
|
+
}
|
|
42
|
+
/** Closes and forgets every session idle longer than `idleMs`. */
|
|
43
|
+
sweep(now, idleMs) {
|
|
44
|
+
for (const [id, session] of this.sessions) {
|
|
45
|
+
if (now - session.lastSeen > idleMs) {
|
|
46
|
+
this.sessions.delete(id);
|
|
47
|
+
void session.transport.close();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async closeAll() {
|
|
52
|
+
for (const [id, session] of this.sessions) {
|
|
53
|
+
this.sessions.delete(id);
|
|
54
|
+
await session.transport.close();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Committed sessions only; reservations are transient. */
|
|
58
|
+
get size() {
|
|
59
|
+
return this.sessions.size;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/http/sessions.ts"],"names":[],"mappings":"AASA;;;;;;GAMG;AACH,MAAM,OAAO,YAAY;IAIM;IAHZ,QAAQ,GAAG,IAAI,GAAG,EAAmB,CAAC;IAC/C,QAAQ,GAAG,CAAC,CAAC;IAErB,YAA6B,WAAmB;QAAnB,gBAAW,GAAX,WAAW,CAAQ;IAAG,CAAC;IAEpD,+EAA+E;IAC/E,OAAO;QACL,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,WAAW;YAAE,OAAO,KAAK,CAAC;QACzE,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,0CAA0C;IAC1C,MAAM,CAAC,EAAU,EAAE,SAAwC;QACzD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;QAC/C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,kEAAkE;IAClE,OAAO;QACL,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,EAAU;QACd,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACtC,IAAI,OAAO;YAAE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7C,CAAC;IAED,MAAM,CAAC,EAAU;QACf,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;IAED,kEAAkE;IAClE,KAAK,CAAC,GAAW,EAAE,MAAc;QAC/B,KAAK,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC1C,IAAI,GAAG,GAAG,OAAO,CAAC,QAAQ,GAAG,MAAM,EAAE,CAAC;gBACpC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACzB,KAAK,OAAO,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,KAAK,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC1C,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACzB,MAAM,OAAO,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QAClC,CAAC;IACH,CAAC;IAED,2DAA2D;IAC3D,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;CACF"}
|
package/dist/http.d.ts
CHANGED
|
@@ -1,58 +1,16 @@
|
|
|
1
|
-
import type { Request } from 'express';
|
|
2
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { type HttpConfig } from './http/config.js';
|
|
3
3
|
/**
|
|
4
4
|
* Streamable HTTP transport.
|
|
5
5
|
*
|
|
6
6
|
* Split out of index.ts because it carries all of the deployment-facing
|
|
7
7
|
* concerns — authentication, origin validation, session lifetime, rate limits —
|
|
8
|
-
* none of which apply to the stdio path.
|
|
8
|
+
* none of which apply to the stdio path. The pieces live under `src/http/`;
|
|
9
|
+
* this module wires them together and owns the process-level lifecycle.
|
|
9
10
|
*/
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
bindHost: string;
|
|
14
|
-
/** When set, every request must present this as a bearer token. */
|
|
15
|
-
authToken?: string;
|
|
16
|
-
/** Host header allowlist. Empty means "accept any", which is only safe behind a proxy. */
|
|
17
|
-
allowedHosts: string[];
|
|
18
|
-
/** Origin header allowlist for browser callers. */
|
|
19
|
-
allowedOrigins: string[];
|
|
20
|
-
rateLimit: number;
|
|
21
|
-
rateWindowMs: number;
|
|
22
|
-
/** Sessions idle longer than this are closed, so the map cannot grow forever. */
|
|
23
|
-
sessionIdleMs: number;
|
|
24
|
-
maxSessions: number;
|
|
25
|
-
}
|
|
26
|
-
export declare function readHttpConfig(): HttpConfig;
|
|
27
|
-
/** Fixed-window counter, per client. */
|
|
28
|
-
export declare class RateLimiter {
|
|
29
|
-
private readonly limit;
|
|
30
|
-
private readonly windowMs;
|
|
31
|
-
private readonly hits;
|
|
32
|
-
constructor(limit: number, windowMs: number);
|
|
33
|
-
check(key: string, now?: number): {
|
|
34
|
-
allowed: boolean;
|
|
35
|
-
remaining: number;
|
|
36
|
-
retryAfter: number;
|
|
37
|
-
};
|
|
38
|
-
sweep(now?: number): void;
|
|
39
|
-
get size(): number;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Compare a presented token against the configured one in constant time, so the
|
|
43
|
-
* endpoint does not leak the token's prefix through response timing.
|
|
44
|
-
*/
|
|
45
|
-
export declare function tokenMatches(presented: string | undefined, expected: string): boolean;
|
|
46
|
-
export declare function bearerToken(header: string | undefined): string | undefined;
|
|
47
|
-
export declare function clientKey(req: Request): string;
|
|
48
|
-
/**
|
|
49
|
-
* Node parses a repeated header into an array, so this field is genuinely
|
|
50
|
-
* `string | string[] | undefined`. Asserting it was always a string meant a
|
|
51
|
-
* request that sent `Mcp-Session-Id` twice reached `sessions.get()` holding an
|
|
52
|
-
* array, missed every session, and was reported as an invalid session ID.
|
|
53
|
-
* Ambiguous is treated as absent: two session IDs identify no session.
|
|
54
|
-
*/
|
|
55
|
-
export declare function sessionIdOf(req: Request): string | undefined;
|
|
11
|
+
export { bearerToken, clientKey, sessionIdOf, tokenMatches } from './http/auth.js';
|
|
12
|
+
export { assertSafeToStart, isLoopback, readHttpConfig, type HttpConfig } from './http/config.js';
|
|
13
|
+
export { RateLimiter } from './http/rate-limiter.js';
|
|
56
14
|
export interface HttpServerHandle {
|
|
57
15
|
close: () => Promise<void>;
|
|
58
16
|
/** Resolves once bound. Port 0 picks a free port, which tests rely on. */
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,EAAqC,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKtF;;;;;;;GAOG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAClG,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAErD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,0EAA0E;IAC1E,KAAK,EAAE,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClC;AAED,wBAAgB,SAAS,CACvB,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,EACzC,MAAM,GAAE,UAA6B,GACpC,gBAAgB,CA4ElB"}
|