@kieksme/mcp-http-kit 0.0.0-stage → 0.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/LICENSE +21 -0
- package/README.md +197 -2
- package/SECURITY.md +19 -0
- package/dist/auth/bearer.d.ts +14 -0
- package/dist/auth/bearer.d.ts.map +1 -0
- package/dist/auth/bearer.js +43 -0
- package/dist/auth/bearer.js.map +1 -0
- package/dist/auth/context.d.ts +7 -0
- package/dist/auth/context.d.ts.map +1 -0
- package/dist/auth/context.js +46 -0
- package/dist/auth/context.js.map +1 -0
- package/dist/auth/index.d.ts +25 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +34 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/oauth.d.ts +25 -0
- package/dist/auth/oauth.d.ts.map +1 -0
- package/dist/auth/oauth.js +161 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +201 -0
- package/dist/config.js.map +1 -0
- package/dist/http/app.d.ts +32 -0
- package/dist/http/app.d.ts.map +1 -0
- package/dist/http/app.js +127 -0
- package/dist/http/app.js.map +1 -0
- package/dist/http/host-guard.d.ts +22 -0
- package/dist/http/host-guard.d.ts.map +1 -0
- package/dist/http/host-guard.js +46 -0
- package/dist/http/host-guard.js.map +1 -0
- package/dist/http/index.d.ts +4 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +4 -0
- package/dist/http/index.js.map +1 -0
- package/dist/http/node.d.ts +36 -0
- package/dist/http/node.d.ts.map +1 -0
- package/dist/http/node.js +74 -0
- package/dist/http/node.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/start.d.ts +16 -0
- package/dist/start.d.ts.map +1 -0
- package/dist/start.js +84 -0
- package/dist/start.js.map +1 -0
- package/dist/test/helpers.d.ts +35 -0
- package/dist/test/helpers.d.ts.map +1 -0
- package/dist/test/helpers.js +171 -0
- package/dist/test/helpers.js.map +1 -0
- package/dist/test/index.d.ts +2 -0
- package/dist/test/index.d.ts.map +1 -0
- package/dist/test/index.js +2 -0
- package/dist/test/index.js.map +1 -0
- package/dist/types.d.ts +123 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/upstream.d.ts +17 -0
- package/dist/upstream.d.ts.map +1 -0
- package/dist/upstream.js +49 -0
- package/dist/upstream.js.map +1 -0
- package/docs/migration.md +125 -0
- package/package.json +87 -4
- package/templates/Dockerfile +46 -0
- package/templates/railway.json +13 -0
- package/templates/server.json +79 -0
- package/templates/smithery.yaml +31 -0
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { KitConfig } from './types.js';
|
|
2
|
+
declare const LOCAL_HOSTS: Set<string>;
|
|
3
|
+
export type LoadConfigOptions = {
|
|
4
|
+
/** Default realm for WWW-Authenticate (server package name). */
|
|
5
|
+
realm?: string;
|
|
6
|
+
/** Override default MCP path. */
|
|
7
|
+
defaultPath?: string;
|
|
8
|
+
/** Override default health path. */
|
|
9
|
+
defaultHealthPath?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Extra health paths (e.g. `['/healthz']`) in addition to the primary path.
|
|
12
|
+
* Also settable via `MCP_HEALTH_ALIASES` (comma-separated).
|
|
13
|
+
*/
|
|
14
|
+
healthPathAliases?: string[];
|
|
15
|
+
/** Override max body size. */
|
|
16
|
+
maxBodyBytes?: number;
|
|
17
|
+
};
|
|
18
|
+
/** Deduplicate path list while preserving order (primary first). */
|
|
19
|
+
export declare function resolveHealthPaths(primary: string, aliases?: string[]): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Load kit configuration from the environment.
|
|
22
|
+
*
|
|
23
|
+
* Canonical names: `MCP_TRANSPORT`, `PORT` / `MCP_HTTP_PORT`, `MCP_HTTP_HOST`,
|
|
24
|
+
* `MCP_HTTP_PATH`, `MCP_ALLOWED_HOSTS`, `MCP_ALLOWED_ORIGINS`, `MCP_AUTH_MODE`,
|
|
25
|
+
* `MCP_AUTH_TOKEN`. Legacy aliases emit warnings but still work.
|
|
26
|
+
*/
|
|
27
|
+
export declare function loadConfig(env?: NodeJS.ProcessEnv, options?: LoadConfigOptions): KitConfig;
|
|
28
|
+
export declare function isLocalhostHostname(hostname: string): boolean;
|
|
29
|
+
export { LOCAL_HOSTS };
|
|
30
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,SAAS,EAEV,MAAM,YAAY,CAAC;AAKpB,QAAA,MAAM,WAAW,aAAsD,CAAC;AAExE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,8BAA8B;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAQF,oEAAoE;AACpE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CAUpF;AAiJD;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,iBAAsB,GAC9B,SAAS,CAyDX;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
const DEFAULT_PATH = '/mcp';
|
|
2
|
+
const DEFAULT_HEALTH = '/health';
|
|
3
|
+
const DEFAULT_MAX_BODY = 4 * 1024 * 1024;
|
|
4
|
+
const LOCAL_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '[::1]']);
|
|
5
|
+
function normalizePath(raw) {
|
|
6
|
+
const trimmed = raw.trim();
|
|
7
|
+
if (!trimmed)
|
|
8
|
+
return trimmed;
|
|
9
|
+
return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
10
|
+
}
|
|
11
|
+
/** Deduplicate path list while preserving order (primary first). */
|
|
12
|
+
export function resolveHealthPaths(primary, aliases = []) {
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
const out = [];
|
|
15
|
+
for (const entry of [primary, ...aliases]) {
|
|
16
|
+
const path = normalizePath(entry);
|
|
17
|
+
if (!path || seen.has(path))
|
|
18
|
+
continue;
|
|
19
|
+
seen.add(path);
|
|
20
|
+
out.push(path);
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
function firstDefined(env, names) {
|
|
25
|
+
for (const name of names) {
|
|
26
|
+
const value = env[name];
|
|
27
|
+
if (value !== undefined && value !== '') {
|
|
28
|
+
return { value, name };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
function splitList(raw) {
|
|
34
|
+
if (!raw)
|
|
35
|
+
return [];
|
|
36
|
+
return raw
|
|
37
|
+
.split(',')
|
|
38
|
+
.map(entry => entry.trim())
|
|
39
|
+
.filter(Boolean);
|
|
40
|
+
}
|
|
41
|
+
function parsePort(raw, label) {
|
|
42
|
+
const port = Number(raw ?? '3000');
|
|
43
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
44
|
+
throw new Error(`${label} must be an integer 0..65535`);
|
|
45
|
+
}
|
|
46
|
+
return port;
|
|
47
|
+
}
|
|
48
|
+
function normalizeHost(host) {
|
|
49
|
+
return host.trim().toLowerCase();
|
|
50
|
+
}
|
|
51
|
+
function isLocalBind(host) {
|
|
52
|
+
return LOCAL_HOSTS.has(normalizeHost(host));
|
|
53
|
+
}
|
|
54
|
+
function parseTransport(raw) {
|
|
55
|
+
const value = (raw ?? 'stdio').trim().toLowerCase();
|
|
56
|
+
if (value === 'stdio' || value === 'http')
|
|
57
|
+
return value;
|
|
58
|
+
throw new Error(`MCP_TRANSPORT must be "stdio" or "http" (got ${JSON.stringify(raw)})`);
|
|
59
|
+
}
|
|
60
|
+
function parseAuthMode(raw) {
|
|
61
|
+
const value = (raw ?? 'bearer').trim().toLowerCase();
|
|
62
|
+
if (value === 'bearer' || value === 'oauth' || value === 'none')
|
|
63
|
+
return value;
|
|
64
|
+
throw new Error(`MCP_AUTH_MODE must be bearer|oauth|none (got ${JSON.stringify(raw)})`);
|
|
65
|
+
}
|
|
66
|
+
function truthy(raw) {
|
|
67
|
+
if (!raw)
|
|
68
|
+
return false;
|
|
69
|
+
return ['1', 'true', 'yes', 'on'].includes(raw.trim().toLowerCase());
|
|
70
|
+
}
|
|
71
|
+
function parseBearerTokens(env, warnings) {
|
|
72
|
+
const primary = firstDefined(env, ['MCP_AUTH_TOKEN']);
|
|
73
|
+
const legacy = firstDefined(env, ['MCP_HTTP_AUTH_TOKEN', 'MCP_SERVER_AUTH_TOKEN']);
|
|
74
|
+
if (primary.value) {
|
|
75
|
+
return splitList(primary.value);
|
|
76
|
+
}
|
|
77
|
+
if (legacy.value && legacy.name) {
|
|
78
|
+
warnings.push(`${legacy.name} is deprecated; use MCP_AUTH_TOKEN (comma-separated list for rotation)`);
|
|
79
|
+
return splitList(legacy.value);
|
|
80
|
+
}
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
function parseOAuthConfig(env) {
|
|
84
|
+
const resource = env.MCP_OAUTH_RESOURCE?.trim();
|
|
85
|
+
const authorizationServers = splitList(env.MCP_OAUTH_AUTHORIZATION_SERVERS);
|
|
86
|
+
if (!resource) {
|
|
87
|
+
throw new Error('MCP_OAUTH_RESOURCE is required when MCP_AUTH_MODE=oauth');
|
|
88
|
+
}
|
|
89
|
+
if (authorizationServers.length === 0) {
|
|
90
|
+
throw new Error('MCP_OAUTH_AUTHORIZATION_SERVERS is required when MCP_AUTH_MODE=oauth');
|
|
91
|
+
}
|
|
92
|
+
const audiences = splitList(env.MCP_OAUTH_AUDIENCES);
|
|
93
|
+
const requiredScopes = splitList(env.MCP_OAUTH_REQUIRED_SCOPES);
|
|
94
|
+
const scopesSupported = splitList(env.MCP_OAUTH_SCOPES_SUPPORTED);
|
|
95
|
+
const jwksUrl = env.MCP_OAUTH_JWKS_URL?.trim();
|
|
96
|
+
return {
|
|
97
|
+
mode: 'oauth',
|
|
98
|
+
resource,
|
|
99
|
+
authorizationServers,
|
|
100
|
+
audiences: audiences.length > 0 ? audiences : [resource],
|
|
101
|
+
requiredScopes,
|
|
102
|
+
scopesSupported: scopesSupported.length > 0 ? scopesSupported : requiredScopes,
|
|
103
|
+
...(jwksUrl ? { jwksUrl } : {}),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function parseAuth(env, http, warnings) {
|
|
107
|
+
const mode = parseAuthMode(env.MCP_AUTH_MODE);
|
|
108
|
+
if (mode === 'bearer') {
|
|
109
|
+
const tokens = parseBearerTokens(env, warnings);
|
|
110
|
+
if (tokens.length === 0) {
|
|
111
|
+
throw new Error('MCP_AUTH_TOKEN is required when MCP_AUTH_MODE=bearer (generate with: openssl rand -hex 32)');
|
|
112
|
+
}
|
|
113
|
+
return { mode: 'bearer', tokens };
|
|
114
|
+
}
|
|
115
|
+
if (mode === 'oauth') {
|
|
116
|
+
return parseOAuthConfig(env);
|
|
117
|
+
}
|
|
118
|
+
const allowPublic = truthy(env.MCP_AUTH_ALLOW_NONE);
|
|
119
|
+
if (!isLocalBind(http.host) && !allowPublic) {
|
|
120
|
+
throw new Error('MCP_AUTH_MODE=none is only allowed on localhost unless MCP_AUTH_ALLOW_NONE=true (public read-only opt-in)');
|
|
121
|
+
}
|
|
122
|
+
return { mode: 'none', allowPublic };
|
|
123
|
+
}
|
|
124
|
+
function parseAllowlists(env, host, warnings) {
|
|
125
|
+
const hostsRaw = firstDefined(env, ['MCP_ALLOWED_HOSTS', 'MCP_HTTP_ALLOWED_HOSTS']);
|
|
126
|
+
if (hostsRaw.name === 'MCP_HTTP_ALLOWED_HOSTS') {
|
|
127
|
+
warnings.push('MCP_HTTP_ALLOWED_HOSTS is deprecated; use MCP_ALLOWED_HOSTS');
|
|
128
|
+
}
|
|
129
|
+
const originsRaw = firstDefined(env, ['MCP_ALLOWED_ORIGINS', 'MCP_HTTP_ALLOWED_ORIGINS']);
|
|
130
|
+
if (originsRaw.name === 'MCP_HTTP_ALLOWED_ORIGINS') {
|
|
131
|
+
warnings.push('MCP_HTTP_ALLOWED_ORIGINS is deprecated; use MCP_ALLOWED_ORIGINS');
|
|
132
|
+
}
|
|
133
|
+
let allowedHosts = splitList(hostsRaw.value).map(normalizeHost);
|
|
134
|
+
if (allowedHosts.length === 0) {
|
|
135
|
+
allowedHosts = ['127.0.0.1', 'localhost', '[::1]'];
|
|
136
|
+
}
|
|
137
|
+
if (!isLocalBind(host) && !hostsRaw.value) {
|
|
138
|
+
throw new Error('MCP_ALLOWED_HOSTS is required when binding beyond localhost (comma-separated hostnames clients use)');
|
|
139
|
+
}
|
|
140
|
+
const allowedOrigins = splitList(originsRaw.value);
|
|
141
|
+
return { allowedHosts, allowedOrigins };
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Load kit configuration from the environment.
|
|
145
|
+
*
|
|
146
|
+
* Canonical names: `MCP_TRANSPORT`, `PORT` / `MCP_HTTP_PORT`, `MCP_HTTP_HOST`,
|
|
147
|
+
* `MCP_HTTP_PATH`, `MCP_ALLOWED_HOSTS`, `MCP_ALLOWED_ORIGINS`, `MCP_AUTH_MODE`,
|
|
148
|
+
* `MCP_AUTH_TOKEN`. Legacy aliases emit warnings but still work.
|
|
149
|
+
*/
|
|
150
|
+
export function loadConfig(env = process.env, options = {}) {
|
|
151
|
+
const warnings = [];
|
|
152
|
+
const transport = parseTransport(env.MCP_TRANSPORT);
|
|
153
|
+
const portSource = firstDefined(env, ['PORT', 'MCP_HTTP_PORT']);
|
|
154
|
+
if (portSource.name === 'MCP_HTTP_PORT' && !env.PORT) {
|
|
155
|
+
warnings.push('MCP_HTTP_PORT is deprecated as the primary port; prefer PORT (Railway-friendly)');
|
|
156
|
+
}
|
|
157
|
+
const host = env.MCP_HTTP_HOST?.trim() || '127.0.0.1';
|
|
158
|
+
const path = env.MCP_HTTP_PATH?.trim() ||
|
|
159
|
+
env.MCP_HTTP_ENDPOINT?.trim() ||
|
|
160
|
+
options.defaultPath ||
|
|
161
|
+
DEFAULT_PATH;
|
|
162
|
+
if (env.MCP_HTTP_ENDPOINT && !env.MCP_HTTP_PATH) {
|
|
163
|
+
warnings.push('MCP_HTTP_ENDPOINT is deprecated; use MCP_HTTP_PATH');
|
|
164
|
+
}
|
|
165
|
+
const healthPath = normalizePath(env.MCP_HEALTH_PATH?.trim() ||
|
|
166
|
+
env.HEALTH_ENDPOINT?.trim() ||
|
|
167
|
+
options.defaultHealthPath ||
|
|
168
|
+
DEFAULT_HEALTH);
|
|
169
|
+
if (env.HEALTH_ENDPOINT && !env.MCP_HEALTH_PATH) {
|
|
170
|
+
warnings.push('HEALTH_ENDPOINT is deprecated; use MCP_HEALTH_PATH');
|
|
171
|
+
}
|
|
172
|
+
const aliasFromEnv = splitList(env.MCP_HEALTH_ALIASES);
|
|
173
|
+
const aliasFromOptions = options.healthPathAliases ?? [];
|
|
174
|
+
const healthPaths = resolveHealthPaths(healthPath, [...aliasFromOptions, ...aliasFromEnv]);
|
|
175
|
+
const { allowedHosts, allowedOrigins } = parseAllowlists(env, host, warnings);
|
|
176
|
+
const port = parsePort(portSource.value, portSource.name ?? 'PORT');
|
|
177
|
+
const http = {
|
|
178
|
+
host,
|
|
179
|
+
port,
|
|
180
|
+
path: normalizePath(path),
|
|
181
|
+
healthPath,
|
|
182
|
+
healthPaths,
|
|
183
|
+
allowedHosts,
|
|
184
|
+
allowedOrigins,
|
|
185
|
+
maxBodyBytes: options.maxBodyBytes ?? DEFAULT_MAX_BODY,
|
|
186
|
+
enableSessionMethods: truthy(env.MCP_HTTP_ENABLE_SESSION_METHODS),
|
|
187
|
+
};
|
|
188
|
+
const auth = transport === 'http' ? parseAuth(env, http, warnings) : { mode: 'none', allowPublic: true };
|
|
189
|
+
return {
|
|
190
|
+
transport,
|
|
191
|
+
http,
|
|
192
|
+
auth: transport === 'http' ? auth : { mode: 'none', allowPublic: true },
|
|
193
|
+
realm: options.realm ?? env.MCP_AUTH_REALM?.trim() ?? 'mcp',
|
|
194
|
+
warnings,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
export function isLocalhostHostname(hostname) {
|
|
198
|
+
return LOCAL_HOSTS.has(normalizeHost(hostname));
|
|
199
|
+
}
|
|
200
|
+
export { LOCAL_HOSTS };
|
|
201
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { resourceMetadataUrl } from '../auth/oauth.js';
|
|
2
|
+
import type { HealthInfoProvider, KitConfig, ServerFactory } from '../types.js';
|
|
3
|
+
export type McpHttpAppOptions = {
|
|
4
|
+
createServer: ServerFactory;
|
|
5
|
+
config: KitConfig;
|
|
6
|
+
/**
|
|
7
|
+
* Optional extra fields for the health JSON body.
|
|
8
|
+
* Merged with `status: "ok"` always winning (cannot be overridden).
|
|
9
|
+
*/
|
|
10
|
+
getHealthInfo?: HealthInfoProvider;
|
|
11
|
+
};
|
|
12
|
+
export type McpHttpApp = {
|
|
13
|
+
/** Web-standard fetch handler (framework-agnostic core). */
|
|
14
|
+
fetch: (request: Request) => Promise<Response>;
|
|
15
|
+
close: () => Promise<void>;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Build a framework-agnostic Streamable HTTP app.
|
|
19
|
+
*
|
|
20
|
+
* Request order:
|
|
21
|
+
* 1. GET health — no auth, **before** Host/Origin checks (Railway-friendly)
|
|
22
|
+
* 2. OAuth protected-resource metadata — no auth, before Host checks
|
|
23
|
+
* 3. Host/Origin allowlist → 403
|
|
24
|
+
* 4. MCP path method gate (POST required unless session methods enabled)
|
|
25
|
+
* 5. Auth → 401
|
|
26
|
+
* 6. MCP handler (`createMcpHandler`) with `authInfo` pass-through
|
|
27
|
+
*/
|
|
28
|
+
export declare function createMcpHttpApp(options: McpHttpAppOptions): McpHttpApp;
|
|
29
|
+
/** Absolute metadata path for OAuth WWW-Authenticate helpers in tests/docs. */
|
|
30
|
+
export declare function protectedResourceMetadataPath(mcpPath: string): string;
|
|
31
|
+
export { resourceMetadataUrl };
|
|
32
|
+
//# sourceMappingURL=app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/http/app.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkC,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvF,OAAO,KAAK,EACV,kBAAkB,EAClB,SAAS,EAET,aAAa,EACd,MAAM,aAAa,CAAC;AAGrB,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,EAAE,aAAa,CAAC;IAC5B,MAAM,EAAE,SAAS,CAAC;IAClB;;;OAGG;IACH,aAAa,CAAC,EAAE,kBAAkB,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,4DAA4D;IAC5D,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/C,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC;AAuBF;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CA6GvE;AAED,+EAA+E;AAC/E,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGrE;AAED,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
|
package/dist/http/app.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createMcpHandler } from '@modelcontextprotocol/server';
|
|
2
|
+
import { authenticateRequest } from '../auth/index.js';
|
|
3
|
+
import { fromAuthInfo, toAuthInfo } from '../auth/context.js';
|
|
4
|
+
import { buildProtectedResourceMetadata, resourceMetadataUrl } from '../auth/oauth.js';
|
|
5
|
+
import { checkHostAndOrigin } from './host-guard.js';
|
|
6
|
+
function jsonResponse(status, body, headers) {
|
|
7
|
+
return new Response(JSON.stringify(body), {
|
|
8
|
+
status,
|
|
9
|
+
headers: { 'content-type': 'application/json', ...headers },
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
function textResponse(status, body, headers) {
|
|
13
|
+
return new Response(body, headers ? { status, headers } : { status });
|
|
14
|
+
}
|
|
15
|
+
function isWellKnownProtectedResource(pathname, mcpPath) {
|
|
16
|
+
if (pathname === '/.well-known/oauth-protected-resource')
|
|
17
|
+
return true;
|
|
18
|
+
const suffix = mcpPath === '/' ? '' : mcpPath.replace(/\/$/, '');
|
|
19
|
+
return pathname === `/.well-known/oauth-protected-resource${suffix}`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build a framework-agnostic Streamable HTTP app.
|
|
23
|
+
*
|
|
24
|
+
* Request order:
|
|
25
|
+
* 1. GET health — no auth, **before** Host/Origin checks (Railway-friendly)
|
|
26
|
+
* 2. OAuth protected-resource metadata — no auth, before Host checks
|
|
27
|
+
* 3. Host/Origin allowlist → 403
|
|
28
|
+
* 4. MCP path method gate (POST required unless session methods enabled)
|
|
29
|
+
* 5. Auth → 401
|
|
30
|
+
* 6. MCP handler (`createMcpHandler`) with `authInfo` pass-through
|
|
31
|
+
*/
|
|
32
|
+
export function createMcpHttpApp(options) {
|
|
33
|
+
const { createServer, config, getHealthInfo } = options;
|
|
34
|
+
const { http, auth, realm } = config;
|
|
35
|
+
const healthPaths = new Set(http.healthPaths);
|
|
36
|
+
const mcpHandler = createMcpHandler(sdkCtx => {
|
|
37
|
+
const request = sdkCtx.requestInfo;
|
|
38
|
+
const context = {
|
|
39
|
+
auth: fromAuthInfo(sdkCtx.authInfo),
|
|
40
|
+
headers: request?.headers ?? new Headers(),
|
|
41
|
+
url: request ? new URL(request.url) : new URL(`http://localhost${http.path}`),
|
|
42
|
+
era: sdkCtx.era,
|
|
43
|
+
...(sdkCtx.authInfo ? { authInfo: sdkCtx.authInfo } : {}),
|
|
44
|
+
};
|
|
45
|
+
return createServer(context);
|
|
46
|
+
}, { maxRequestBodySize: http.maxBodyBytes });
|
|
47
|
+
const fetch = async (request) => {
|
|
48
|
+
const url = new URL(request.url);
|
|
49
|
+
const { pathname } = url;
|
|
50
|
+
// 1. Health — unauthenticated, no Host gate (load balancers / Railway probes)
|
|
51
|
+
if (request.method === 'GET' && healthPaths.has(pathname)) {
|
|
52
|
+
const extra = getHealthInfo ? await getHealthInfo() : {};
|
|
53
|
+
return jsonResponse(200, { ...extra, status: 'ok' });
|
|
54
|
+
}
|
|
55
|
+
// 2. OAuth protected resource metadata (RFC 9728)
|
|
56
|
+
if (auth.mode === 'oauth' &&
|
|
57
|
+
request.method === 'GET' &&
|
|
58
|
+
isWellKnownProtectedResource(pathname, http.path)) {
|
|
59
|
+
return jsonResponse(200, buildProtectedResourceMetadata(auth), {
|
|
60
|
+
'cache-control': 'public, max-age=60',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// 3. Host / Origin allowlist
|
|
64
|
+
const hostCheck = checkHostAndOrigin({
|
|
65
|
+
host: request.headers.get('host'),
|
|
66
|
+
origin: request.headers.get('origin'),
|
|
67
|
+
}, {
|
|
68
|
+
allowedHosts: http.allowedHosts,
|
|
69
|
+
allowedOrigins: http.allowedOrigins,
|
|
70
|
+
});
|
|
71
|
+
if (!hostCheck.ok) {
|
|
72
|
+
return textResponse(403, 'Forbidden');
|
|
73
|
+
}
|
|
74
|
+
// Non-MCP paths
|
|
75
|
+
if (pathname !== http.path) {
|
|
76
|
+
return textResponse(404, 'Not found');
|
|
77
|
+
}
|
|
78
|
+
// 4. Method gate — stateless Streamable HTTP is POST-only by default
|
|
79
|
+
if (request.method !== 'POST') {
|
|
80
|
+
if (http.enableSessionMethods &&
|
|
81
|
+
(request.method === 'GET' || request.method === 'DELETE')) {
|
|
82
|
+
// fall through to MCP handler
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
return new Response('Method Not Allowed', {
|
|
86
|
+
status: 405,
|
|
87
|
+
headers: { allow: http.enableSessionMethods ? 'GET, POST, DELETE' : 'POST' },
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// 5. Auth
|
|
92
|
+
const authResult = await authenticateRequest({
|
|
93
|
+
auth,
|
|
94
|
+
realm,
|
|
95
|
+
authorizationHeader: request.headers.get('authorization'),
|
|
96
|
+
});
|
|
97
|
+
if (!authResult.ok) {
|
|
98
|
+
const headers = {
|
|
99
|
+
'www-authenticate': authResult.wwwAuthenticate,
|
|
100
|
+
};
|
|
101
|
+
if (authResult.contentType)
|
|
102
|
+
headers['content-type'] = authResult.contentType;
|
|
103
|
+
return new Response(authResult.body, { status: authResult.status, headers });
|
|
104
|
+
}
|
|
105
|
+
const authInfo = toAuthInfo(authResult.context, config);
|
|
106
|
+
try {
|
|
107
|
+
return await mcpHandler.fetch(request, authInfo ? { authInfo } : undefined);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
console.error('MCP HTTP request failed:', error instanceof Error ? error.message : error);
|
|
111
|
+
return textResponse(500, 'Server error');
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
fetch,
|
|
116
|
+
close: async () => {
|
|
117
|
+
await mcpHandler.close();
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** Absolute metadata path for OAuth WWW-Authenticate helpers in tests/docs. */
|
|
122
|
+
export function protectedResourceMetadataPath(mcpPath) {
|
|
123
|
+
const suffix = mcpPath === '/' ? '' : mcpPath.replace(/\/$/, '');
|
|
124
|
+
return `/.well-known/oauth-protected-resource${suffix}`;
|
|
125
|
+
}
|
|
126
|
+
export { resourceMetadataUrl };
|
|
127
|
+
//# sourceMappingURL=app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/http/app.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type HostGuardConfig = {
|
|
2
|
+
allowedHosts: string[];
|
|
3
|
+
/** Absolute origin allowlist (e.g. `https://app.example.com`). Empty = derive from hosts. */
|
|
4
|
+
allowedOrigins: string[];
|
|
5
|
+
};
|
|
6
|
+
export type HostGuardResult = {
|
|
7
|
+
ok: true;
|
|
8
|
+
} | {
|
|
9
|
+
ok: false;
|
|
10
|
+
reason: string;
|
|
11
|
+
};
|
|
12
|
+
declare function normalizeHostname(hostHeader: string | null | undefined): string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* DNS-rebinding protection: require Host in the allowlist and Origin (when present)
|
|
15
|
+
* to match allowed hosts/origins.
|
|
16
|
+
*/
|
|
17
|
+
export declare function checkHostAndOrigin(headers: {
|
|
18
|
+
host?: string | null;
|
|
19
|
+
origin?: string | null;
|
|
20
|
+
}, config: HostGuardConfig): HostGuardResult;
|
|
21
|
+
export { normalizeHostname };
|
|
22
|
+
//# sourceMappingURL=host-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-guard.d.ts","sourceRoot":"","sources":["../../src/http/host-guard.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,6FAA6F;IAC7F,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3E,iBAAS,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CASpF;AAoBD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EACzD,MAAM,EAAE,eAAe,GACtB,eAAe,CAYjB;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
function normalizeHostname(hostHeader) {
|
|
2
|
+
if (!hostHeader)
|
|
3
|
+
return undefined;
|
|
4
|
+
// Strip port; handle IPv6 "[::1]:3000"
|
|
5
|
+
const trimmed = hostHeader.trim().toLowerCase();
|
|
6
|
+
if (trimmed.startsWith('[')) {
|
|
7
|
+
const end = trimmed.indexOf(']');
|
|
8
|
+
if (end !== -1)
|
|
9
|
+
return trimmed.slice(0, end + 1);
|
|
10
|
+
}
|
|
11
|
+
return trimmed.replace(/:\d+$/, '');
|
|
12
|
+
}
|
|
13
|
+
function originAllowed(origin, allowedHosts, allowedOrigins) {
|
|
14
|
+
if (!origin)
|
|
15
|
+
return true; // non-browser clients omit Origin
|
|
16
|
+
if (allowedOrigins.length > 0) {
|
|
17
|
+
return allowedOrigins.includes(origin);
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const hostname = new URL(origin).hostname.toLowerCase();
|
|
21
|
+
const bracketed = hostname.includes(':') ? `[${hostname}]` : hostname;
|
|
22
|
+
return allowedHosts.includes(hostname) || allowedHosts.includes(bracketed);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* DNS-rebinding protection: require Host in the allowlist and Origin (when present)
|
|
30
|
+
* to match allowed hosts/origins.
|
|
31
|
+
*/
|
|
32
|
+
export function checkHostAndOrigin(headers, config) {
|
|
33
|
+
const hostname = normalizeHostname(headers.host ?? null);
|
|
34
|
+
if (!hostname) {
|
|
35
|
+
return { ok: false, reason: 'missing Host header' };
|
|
36
|
+
}
|
|
37
|
+
if (!config.allowedHosts.includes(hostname)) {
|
|
38
|
+
return { ok: false, reason: `Host ${hostname} is not allowed` };
|
|
39
|
+
}
|
|
40
|
+
if (!originAllowed(headers.origin ?? null, config.allowedHosts, config.allowedOrigins)) {
|
|
41
|
+
return { ok: false, reason: 'Origin is not allowed' };
|
|
42
|
+
}
|
|
43
|
+
return { ok: true };
|
|
44
|
+
}
|
|
45
|
+
export { normalizeHostname };
|
|
46
|
+
//# sourceMappingURL=host-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-guard.js","sourceRoot":"","sources":["../../src/http/host-guard.ts"],"names":[],"mappings":"AAQA,SAAS,iBAAiB,CAAC,UAAqC;IAC9D,IAAI,CAAC,UAAU;QAAE,OAAO,SAAS,CAAC;IAClC,uCAAuC;IACvC,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAChD,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,GAAG,KAAK,CAAC,CAAC;YAAE,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,aAAa,CACpB,MAAiC,EACjC,YAAsB,EACtB,cAAwB;IAExB,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC,CAAC,kCAAkC;IAC5D,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,OAAO,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACzC,CAAC;IACD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QACxD,MAAM,SAAS,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;QACtE,OAAO,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC7E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAyD,EACzD,MAAuB;IAEvB,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;IACzD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;IACtD,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,QAAQ,iBAAiB,EAAE,CAAC;IAClE,CAAC;IACD,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC;QACvF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC;IACxD,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createMcpHttpApp, protectedResourceMetadataPath, resourceMetadataUrl, type McpHttpApp, type McpHttpAppOptions, } from './app.js';
|
|
2
|
+
export { checkHostAndOrigin, normalizeHostname, type HostGuardConfig, type HostGuardResult, } from './host-guard.js';
|
|
3
|
+
export { createNodeHttpServer, createNodeRequestListener, handleWithKitApp, type NodeHttpServerOptions, } from './node.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/http/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,6BAA6B,EAC7B,mBAAmB,EACnB,KAAK,UAAU,EACf,KAAK,iBAAiB,GACvB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,eAAe,GACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,oBAAoB,EACpB,yBAAyB,EACzB,gBAAgB,EAChB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createMcpHttpApp, protectedResourceMetadataPath, resourceMetadataUrl, } from './app.js';
|
|
2
|
+
export { checkHostAndOrigin, normalizeHostname, } from './host-guard.js';
|
|
3
|
+
export { createNodeHttpServer, createNodeRequestListener, handleWithKitApp, } from './node.js';
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/http/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,6BAA6B,EAC7B,mBAAmB,GAGpB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,kBAAkB,EAClB,iBAAiB,GAGlB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,oBAAoB,EACpB,yBAAyB,EACzB,gBAAgB,GAEjB,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type RequestListener, type Server as HttpServer } from 'node:http';
|
|
2
|
+
import { toWebRequest } from '@modelcontextprotocol/node';
|
|
3
|
+
import type { KitConfig } from '../types.js';
|
|
4
|
+
import type { McpHttpApp } from './app.js';
|
|
5
|
+
export type NodeHttpServerOptions = {
|
|
6
|
+
app: McpHttpApp;
|
|
7
|
+
config: KitConfig;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Adapt a kit HTTP app to a Node `http.Server` request listener.
|
|
11
|
+
* Health/auth/host gates run in the web-standard app; this only bridges I/O.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createNodeRequestListener(options: NodeHttpServerOptions): RequestListener;
|
|
14
|
+
/** Create a listening-ready `node:http` server for the kit app. */
|
|
15
|
+
export declare function createNodeHttpServer(options: NodeHttpServerOptions): HttpServer;
|
|
16
|
+
/**
|
|
17
|
+
* Fastify preHandler / raw hook helper: convert the Fastify request into a
|
|
18
|
+
* web `Request`, run the kit app, and send the `Response` back.
|
|
19
|
+
*
|
|
20
|
+
* Example:
|
|
21
|
+
* ```ts
|
|
22
|
+
* fastify.all('/mcp', async (req, reply) => {
|
|
23
|
+
* await handleWithKitApp(app, req.raw, reply.raw, config);
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export type KitNodeResponse = {
|
|
28
|
+
writeHead: (statusCode: number, headers?: Record<string, string>) => unknown;
|
|
29
|
+
write: (chunk: string | Uint8Array) => unknown;
|
|
30
|
+
end: (chunk?: string | Uint8Array) => unknown;
|
|
31
|
+
on: (event: string, listener: (...args: unknown[]) => void) => unknown;
|
|
32
|
+
headersSent?: boolean;
|
|
33
|
+
destroyed?: boolean;
|
|
34
|
+
};
|
|
35
|
+
export declare function handleWithKitApp(app: McpHttpApp, req: Parameters<typeof toWebRequest>[0], res: KitNodeResponse, config: KitConfig, parsedBody?: unknown): Promise<void>;
|
|
36
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/http/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AAC1F,OAAO,EAAiB,YAAY,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,MAAM,qBAAqB,GAAG;IAClC,GAAG,EAAE,UAAU,CAAC;IAChB,MAAM,EAAE,SAAS,CAAC;CACnB,CAAC;AAUF;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,qBAAqB,GAAG,eAAe,CAgDzF;AAED,mEAAmE;AACnE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,UAAU,CAE/E;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,OAAO,CAAC;IAC7E,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,KAAK,OAAO,CAAC;IAC/C,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,UAAU,KAAK,OAAO,CAAC;IAC9C,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,KAAK,OAAO,CAAC;IACvE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,UAAU,EACf,GAAG,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,EACvC,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,SAAS,EACjB,UAAU,CAAC,EAAE,OAAO,GACnB,OAAO,CAAC,IAAI,CAAC,CAkBf"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { toNodeHandler, toWebRequest } from '@modelcontextprotocol/node';
|
|
3
|
+
function headersToRecord(headers) {
|
|
4
|
+
const out = {};
|
|
5
|
+
headers.forEach((value, key) => {
|
|
6
|
+
out[key] = value;
|
|
7
|
+
});
|
|
8
|
+
return out;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Adapt a kit HTTP app to a Node `http.Server` request listener.
|
|
12
|
+
* Health/auth/host gates run in the web-standard app; this only bridges I/O.
|
|
13
|
+
*/
|
|
14
|
+
export function createNodeRequestListener(options) {
|
|
15
|
+
const { app, config } = options;
|
|
16
|
+
const nodeHandler = toNodeHandler({
|
|
17
|
+
fetch: request => app.fetch(request),
|
|
18
|
+
}, { maxRequestBodySize: config.http.maxBodyBytes });
|
|
19
|
+
return (req, res) => {
|
|
20
|
+
// For non-MCP paths (health, well-known, 403/404/401) we still go through
|
|
21
|
+
// toWebRequest + app.fetch so Host/health ordering stays in one place.
|
|
22
|
+
// toNodeHandler also works, but health probes with weird Host headers must
|
|
23
|
+
// hit app.fetch before any Host rejection — app.fetch already does that.
|
|
24
|
+
void (async () => {
|
|
25
|
+
try {
|
|
26
|
+
const urlPath = req.url ?? '/';
|
|
27
|
+
const pathname = urlPath.split('?')[0] ?? '/';
|
|
28
|
+
const isHealth = req.method === 'GET' && config.http.healthPaths.includes(pathname);
|
|
29
|
+
const isWellKnown = req.method === 'GET' && pathname.startsWith('/.well-known/oauth-protected-resource');
|
|
30
|
+
// Cast: Node's IncomingMessage is structurally compatible; EOPT disagrees on optionals.
|
|
31
|
+
const nodeReq = req;
|
|
32
|
+
if (isHealth || isWellKnown || pathname !== config.http.path) {
|
|
33
|
+
const webRequest = await toWebRequest(nodeReq, undefined, {
|
|
34
|
+
maxRequestBodySize: config.http.maxBodyBytes,
|
|
35
|
+
});
|
|
36
|
+
const response = await app.fetch(webRequest);
|
|
37
|
+
res.writeHead(response.status, headersToRecord(response.headers));
|
|
38
|
+
const buf = Buffer.from(await response.arrayBuffer());
|
|
39
|
+
res.end(buf);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
await nodeHandler(nodeReq, res);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.error('Node HTTP adapter failed:', error instanceof Error ? error.message : error);
|
|
46
|
+
if (!res.headersSent) {
|
|
47
|
+
res.writeHead(500).end('Server error');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
})();
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** Create a listening-ready `node:http` server for the kit app. */
|
|
54
|
+
export function createNodeHttpServer(options) {
|
|
55
|
+
return createServer(createNodeRequestListener(options));
|
|
56
|
+
}
|
|
57
|
+
export async function handleWithKitApp(app, req, res, config, parsedBody) {
|
|
58
|
+
// Prefer streaming-capable Node adapter for the MCP path.
|
|
59
|
+
const pathname = new URL(req.url ?? '/', 'http://localhost').pathname;
|
|
60
|
+
if (pathname === config.http.path) {
|
|
61
|
+
const nodeHandler = toNodeHandler({ fetch: request => app.fetch(request) }, { maxRequestBodySize: config.http.maxBodyBytes });
|
|
62
|
+
await nodeHandler(req, res, parsedBody);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const webRequest = await toWebRequest(req, parsedBody, {
|
|
66
|
+
maxRequestBodySize: config.http.maxBodyBytes,
|
|
67
|
+
});
|
|
68
|
+
const response = await app.fetch(webRequest);
|
|
69
|
+
if (res.headersSent)
|
|
70
|
+
return;
|
|
71
|
+
res.writeHead(response.status, headersToRecord(response.headers));
|
|
72
|
+
res.end(Buffer.from(await response.arrayBuffer()));
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=node.js.map
|