@j0hanz/filesystem-mcp 2.1.4 → 2.1.6
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/dist/cli.d.ts +2 -14
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -22
- package/dist/cli.js.map +1 -1
- package/dist/core/cursor.d.ts +2 -14
- package/dist/core/cursor.d.ts.map +1 -1
- package/dist/core/cursor.js +25 -39
- package/dist/core/cursor.js.map +1 -1
- package/dist/core/diff.d.ts +6 -0
- package/dist/core/diff.d.ts.map +1 -0
- package/dist/core/diff.js +21 -0
- package/dist/core/diff.js.map +1 -0
- package/dist/core/errors.d.ts +2 -31
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +15 -86
- package/dist/core/errors.js.map +1 -1
- package/dist/core/file-uri.d.ts +18 -2
- package/dist/core/file-uri.d.ts.map +1 -1
- package/dist/core/file-uri.js +34 -6
- package/dist/core/file-uri.js.map +1 -1
- package/dist/core/fmt.js +5 -5
- package/dist/core/fmt.js.map +1 -1
- package/dist/core/fs.d.ts +4 -13
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +4 -41
- package/dist/core/fs.js.map +1 -1
- package/dist/core/glob.d.ts +1 -7
- package/dist/core/glob.d.ts.map +1 -1
- package/dist/core/glob.js +1 -11
- package/dist/core/glob.js.map +1 -1
- package/dist/core/input-required.d.ts +2 -10
- package/dist/core/input-required.d.ts.map +1 -1
- package/dist/core/input-required.js +3 -12
- package/dist/core/input-required.js.map +1 -1
- package/dist/core/path-completer.d.ts.map +1 -1
- package/dist/core/path-completer.js +3 -7
- package/dist/core/path-completer.js.map +1 -1
- package/dist/core/path-discovery.js +2 -2
- package/dist/core/path-discovery.js.map +1 -1
- package/dist/core/path.d.ts +10 -25
- package/dist/core/path.d.ts.map +1 -1
- package/dist/core/path.js +28 -66
- package/dist/core/path.js.map +1 -1
- package/dist/core/primitives.d.ts +8 -3
- package/dist/core/primitives.d.ts.map +1 -1
- package/dist/core/primitives.js +10 -5
- package/dist/core/primitives.js.map +1 -1
- package/dist/core/read.d.ts +0 -16
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +19 -56
- package/dist/core/read.js.map +1 -1
- package/dist/core/schema.d.ts +1 -32
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +7 -66
- package/dist/core/schema.js.map +1 -1
- package/dist/core/search.d.ts +16 -5
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +118 -108
- package/dist/core/search.js.map +1 -1
- package/dist/core/sensitive.d.ts.map +1 -1
- package/dist/core/sensitive.js +7 -15
- package/dist/core/sensitive.js.map +1 -1
- package/dist/core/store.d.ts +1 -3
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +1 -5
- package/dist/core/store.js.map +1 -1
- package/dist/core/watcher-registry.d.ts +1 -8
- package/dist/core/watcher-registry.d.ts.map +1 -1
- package/dist/core/watcher-registry.js +3 -12
- package/dist/core/watcher-registry.js.map +1 -1
- package/dist/index.js +1 -8
- package/dist/index.js.map +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +7 -33
- package/dist/prompts.js.map +1 -1
- package/dist/resources.d.ts +1 -1
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +3 -9
- package/dist/resources.js.map +1 -1
- package/dist/server.d.ts +0 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -5
- package/dist/server.js.map +1 -1
- package/dist/tools/batch.d.ts +5 -69
- package/dist/tools/batch.d.ts.map +1 -1
- package/dist/tools/batch.js +7 -117
- package/dist/tools/batch.js.map +1 -1
- package/dist/tools/create.d.ts.map +1 -1
- package/dist/tools/create.js +8 -18
- package/dist/tools/create.js.map +1 -1
- package/dist/tools/define.d.ts +1 -4
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js +6 -24
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/delete-file.d.ts.map +1 -1
- package/dist/tools/delete-file.js +6 -6
- package/dist/tools/delete-file.js.map +1 -1
- package/dist/tools/diff.d.ts.map +1 -1
- package/dist/tools/diff.js +3 -10
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +43 -87
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +0 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list.js +3 -2
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/move.d.ts.map +1 -1
- package/dist/tools/move.js +169 -119
- package/dist/tools/move.js.map +1 -1
- package/dist/tools/patch.d.ts.map +1 -1
- package/dist/tools/patch.js +5 -23
- package/dist/tools/patch.js.map +1 -1
- package/dist/tools/progress.d.ts +0 -1
- package/dist/tools/progress.d.ts.map +1 -1
- package/dist/tools/progress.js +2 -8
- package/dist/tools/progress.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +21 -25
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/replace-in-files.d.ts.map +1 -1
- package/dist/tools/replace-in-files.js +25 -44
- package/dist/tools/replace-in-files.js.map +1 -1
- package/dist/tools/search-content.d.ts.map +1 -1
- package/dist/tools/search-content.js +17 -49
- package/dist/tools/search-content.js.map +1 -1
- package/dist/tools/search-files.d.ts.map +1 -1
- package/dist/tools/search-files.js +1 -2
- package/dist/tools/search-files.js.map +1 -1
- package/dist/tools/stat.d.ts.map +1 -1
- package/dist/tools/stat.js +5 -6
- package/dist/tools/stat.js.map +1 -1
- package/dist/transport/http-policy.d.ts +6 -0
- package/dist/transport/http-policy.d.ts.map +1 -1
- package/dist/transport/http-policy.js +2 -2
- package/dist/transport/http-policy.js.map +1 -1
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +4 -11
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/stdio.js +1 -1
- package/dist/transport/stdio.js.map +1 -1
- package/dist/transport.d.ts +1 -3
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +5 -2
- package/dist/transport.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-policy.js","sourceRoot":"","sources":["../../src/transport/http-policy.ts"],"names":[],"mappings":"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","sourcesContent":["import { getOAuthProtectedResourceMetadataUrl } from '@modelcontextprotocol/express';\nimport { localhostAllowedHostnames } from '@modelcontextprotocol/server';\nimport type { AuthInfo } from '@modelcontextprotocol/server';\n\nimport { createHash, timingSafeEqual } from 'node:crypto';\n\nimport type { NextFunction, Request, RequestHandler, Response } from 'express';\n\nimport { ErrorCode, FsError } from '../core/errors.js';\nimport { Logger } from '../core/observability.js';\nimport { splitCsvList } from '../core/util.js';\nimport { jsonRpcError } from './shared.js';\n\nconst MAX_BEARER_TOKEN_LENGTH = 4096;\n\n/**\n * JSON-RPC's server-defined error range. No SDK enum maps to \"Unauthorized\" or\n * \"Rate limit exceeded\", both of which this module refuses before any handler\n * has parsed a request.\n */\nconst JSONRPC_SERVER_ERROR = -32000;\n\n/**\n * The one way an HTTP refusal goes out — this module's pre-handler rejections\n * and `http.ts`'s route-level ones. The envelope itself is `jsonRpcError` in\n * `shared.ts`, so the stdio leg sends the same literal; this wrapper owns only\n * what HTTP adds on top.\n *\n * `headers` carries what a particular refusal owes the client on top of the\n * JSON content type — `WWW-Authenticate` on a 401, `Retry-After` on a 429.\n * Goes out through `res.json` so every refusal keeps the `charset=utf-8` and\n * `Content-Length` a hand-rolled `writeHead`/`end` pair drops.\n */\nexport function sendJsonRpcError(\n res: Response,\n status: number,\n code: number,\n message: string,\n id: string | number | null = null,\n headers: Record<string, string> = {},\n): void {\n res\n .status(status)\n .set(headers)\n .json(jsonRpcError(code, message, id));\n}\n\nconst LOCALHOST_ORIGIN_RE = /^https?:\\/\\/(localhost|127\\.0\\.0\\.1|\\[::1\\])(:\\d+)?$/u;\n\n/**\n * Pure HTTP auth and binding policy. Holds no state; all functions are\n * directly testable without spinning up a server.\n */\nexport function isLoopbackHttpHost(host: string): boolean {\n const normalizedHost = host.trim().toLowerCase();\n return (\n normalizedHost === '127.0.0.1' ||\n normalizedHost === 'localhost' ||\n normalizedHost === '[::1]' ||\n normalizedHost === '::1'\n );\n}\n\nexport function isAllowedLocalhostOrigin(origin: string): boolean {\n return LOCALHOST_ORIGIN_RE.test(origin);\n}\n\n/** A bind to every interface. Clients never send `Host: 0.0.0.0`, so this bind\n * cannot derive its allowed-host set from itself. */\nfunction isWildcardHttpHost(host: string): boolean {\n return host === '0.0.0.0' || host === '::';\n}\n\nfunction originHostname(origin: string): string | undefined {\n try {\n return new URL(origin).hostname;\n } catch {\n return undefined;\n }\n}\n\n/**\n * True if `origin` (a raw `Origin` request header) is allowed given the\n * env-derived `allowedHostnames` set (hostname-form, no scheme/port). Localhost\n * origins are always accepted via {@link isAllowedLocalhostOrigin}; a remote\n * origin is accepted iff its parsed hostname is in the set. Both the SDK app's\n * `allowedOrigins` and this OPTIONS-handler check consume hostname-form, so a\n * remote origin allowed via `FS_ALLOWED_ORIGINS` is reflected\n * end-to-end in `Access-Control-Allow-Origin`.\n */\nexport function isOriginAllowed(origin: string, allowedHostnames: readonly string[]): boolean {\n if (isAllowedLocalhostOrigin(origin)) return true;\n const host = originHostname(origin);\n return host !== undefined && allowedHostnames.includes(host);\n}\n\nexport function validateBearerAuthorization(\n apiKey: string,\n authHeader: unknown,\n): authHeader is string {\n const bearerPrefix = 'Bearer ';\n if (typeof authHeader !== 'string' || !authHeader.startsWith(bearerPrefix)) {\n return false;\n }\n const userKey = authHeader.slice(bearerPrefix.length).trim();\n const normalizedApiKey = apiKey.trim();\n if (userKey.length > MAX_BEARER_TOKEN_LENGTH || normalizedApiKey.length === 0) {\n return false;\n }\n\n // Pure: hash per call. createHash is negligible next to the timingSafeEqual\n // already done per request, and avoiding module-level cache state keeps this\n // testable without post-import env-mutation footguns.\n const expectedHash = createHash('sha256').update(normalizedApiKey).digest();\n const actualHash = createHash('sha256').update(userKey).digest();\n return timingSafeEqual(expectedHash, actualHash);\n}\n\nfunction isSecureApiKey(key: string | undefined): boolean {\n return typeof key === 'string' && key.trim().length >= 16;\n}\n\n/**\n * Refuse to bind to a non-loopback host without an API key. Throws on\n * policy violation; returns silently when allowed.\n */\nexport function assertHttpBindingPolicy(host: string, apiKey: string | undefined): void {\n if (isLoopbackHttpHost(host)) {\n if (apiKey !== undefined && !isSecureApiKey(apiKey)) {\n throw new FsError(\n ErrorCode.PERMISSION_DENIED,\n 'API_KEY is configured but is insecure (minimum 16 characters).',\n );\n }\n return;\n }\n if (isSecureApiKey(apiKey)) return;\n throw new FsError(\n ErrorCode.PERMISSION_DENIED,\n `Refusing to bind HTTP server to non-loopback host '${host}' without a secure API_KEY (minimum 16 characters).`,\n );\n}\n\n/**\n * The Host header values this bind accepts. `FS_ALLOWED_HOSTS` wins\n * when set; otherwise a loopback bind takes the whole localhost hostname set (a\n * client dialing http://localhost:<port> sends `Host: localhost`, which a bare\n * ['127.0.0.1'] list would 403), a wildcard bind takes none, and a concrete\n * non-loopback bind takes itself. An empty result means no Host validation is\n * mounted — only reachable under FS_ALLOW_UNRESTRICTED_HOSTS=1.\n */\nexport function resolveAllowedHosts(\n httpHost: string,\n allowedHostsEnv: string | undefined,\n): string[] {\n const configured = splitCsvList(allowedHostsEnv);\n if (configured.length > 0) return configured;\n if (isLoopbackHttpHost(httpHost)) return localhostAllowedHostnames();\n if (isWildcardHttpHost(httpHost)) return [];\n return [httpHost];\n}\n\n/**\n * Express's `trust proxy` setting from `FS_TRUST_PROXY`. A\n * non-negative integer hop count parses to a number (Express counts hops from\n * the socket); anything else (a subnet name/expression, or a negative/\n * non-integer string) passes through unchanged for Express to interpret.\n * `undefined` means the env var was unset or empty — leave Express's default\n * (disabled) in place.\n */\nexport function resolveTrustProxySetting(value: string | undefined): number | string | undefined {\n if (!value) return undefined;\n const hops = Number(value);\n return Number.isInteger(hops) && hops >= 0 ? hops : value;\n}\n\n/**\n * Refuse to bind a wildcard host (`0.0.0.0` / `::`) without an explicit\n * `FS_ALLOWED_HOSTS` list. Clients never send `Host: 0.0.0.0`, so\n * defaulting the allowed-host set to the wildcard string would reject all real\n * traffic. Operators who accept the risk can set\n * `FS_ALLOW_UNRESTRICTED_HOSTS=1` to restore warn-and-bind.\n * Loopback and concrete non-loopback hosts are unaffected.\n */\nexport function assertHttpHostPolicy(\n host: string,\n allowedHosts: readonly string[],\n allowUnrestricted: boolean,\n): void {\n if (isLoopbackHttpHost(host)) return;\n // concrete non-loopback: Host validated against the bind host.\n if (!isWildcardHttpHost(host)) return;\n if (allowUnrestricted) return;\n if (allowedHosts.length > 0) return;\n throw new FsError(\n ErrorCode.PERMISSION_DENIED,\n `Refusing to bind wildcard host '${host}' without FS_ALLOWED_HOSTS. Set it to the public hostname(s) clients send, or set FS_ALLOW_UNRESTRICTED_HOSTS=true to accept the risk.`,\n );\n}\n\n// ─── Protected-resource discovery (RFC 9728 / RFC 6750) ──────────────────────\n\n/**\n * This server is a resource server with no authorization server: `API_KEY` is a\n * static secret the operator hands out of band, not an issued token. So the\n * metadata document deliberately omits `authorization_servers` — RFC 9728 §2\n * makes it optional, and its absence is the accurate statement that a token\n * cannot be obtained from an endpoint. `mcpAuthMetadataRouter` from\n * `@modelcontextprotocol/express` is the tool for the IdP-backed case: it\n * requires RFC 8414 authorization-server metadata, and inventing an issuer with\n * endpoints that answer nothing would send clients into a flow that cannot\n * complete. Adopt it if this ever moves to a real IdP.\n *\n * What discovery buys here: a client hitting 401 learns the resource identifier\n * and that the credential goes in the Authorization header, instead of a bare\n * challenge plus a 404 on the well-known path.\n */\nexport function protectedResourceUrl(\n req: Request,\n hostValidated: boolean,\n configured: string | undefined,\n): URL | null {\n if (configured) {\n const parsed = URL.parse(configured);\n if (parsed) return parsed;\n Logger.warn(\n `[HTTP] Ignoring unparseable FS_PUBLIC_URL: ${configured}. Deriving the resource identifier from the Host header instead.`,\n );\n }\n // With no Host validator mounted (FS_ALLOW_UNRESTRICTED_HOSTS=1)\n // the Host header is attacker-controlled, and naming a resource from it would\n // publish an identifier the operator does not own. Answer with nothing instead.\n if (!hostValidated) return null;\n const host = req.headers.host ?? '127.0.0.1';\n return URL.parse(`${req.secure ? 'https' : 'http'}://${host}/mcp`);\n}\n\n/**\n * RFC 6750 §3: a request with no credentials gets a bare challenge, while one\n * that presented something invalid gets `error=\"invalid_token\"`. Clients use\n * the difference to tell \"you must authenticate\" from \"your token is wrong\".\n */\nfunction buildAuthChallenge(\n req: Request,\n hasCredentials: boolean,\n hostValidated: boolean,\n publicUrl: string | undefined,\n): string {\n const resource = protectedResourceUrl(req, hostValidated, publicUrl);\n const params: string[] = [];\n if (resource) {\n params.push(`resource_metadata=\"${getOAuthProtectedResourceMetadataUrl(resource)}\"`);\n }\n if (hasCredentials) {\n params.push('error=\"invalid_token\"', 'error_description=\"Invalid or expired bearer token\"');\n }\n // A bare `Bearer` is a complete challenge; avoid emitting a trailing space.\n return params.length > 0 ? `Bearer ${params.join(', ')}` : 'Bearer';\n}\n\n/**\n * Express middleware: when `apiKey` is set, require a matching bearer token.\n * No key set = open access (loopback dev mode). `apiKey` is captured once per\n * app setup (passed in from startHttpServer) so the middleware and\n * assertHttpBindingPolicy share one source of truth.\n *\n * `requireBearerAuth` + `verifyAccessToken` from `@modelcontextprotocol/express`\n * is the tool for verifier-backed tokens; this middleware stays hand-rolled\n * because the credential is a static operator key with no verifier, the 401 body\n * must stay a JSON-RPC envelope, and the RFC 6750 bare-vs-`invalid_token`\n * challenge split is implemented here. Adopt `requireBearerAuth` if this ever\n * verifies issued tokens.\n */\nexport function bearerAuthMiddleware(\n apiKey: string | undefined,\n hostValidated: boolean,\n publicUrl?: string,\n): RequestHandler {\n return (req: Request, res: Response, next: NextFunction): void => {\n if (!apiKey) {\n next();\n return;\n }\n const authHeader = req.headers.authorization;\n if (isSecureApiKey(apiKey) && validateBearerAuthorization(apiKey, authHeader)) {\n // Forward the validated caller to the SDK pipeline: toNodeHandler reads\n // req.auth and passes it as the handler's pass-through authInfo, which the\n // per-request factory receives and tool handlers read as ctx.http.authInfo.\n // `expiresAt` is deliberately omitted: a static API key has no expiry and\n // there is no `exp` claim or introspection response to read one from.\n // Populate it from the token if this ever moves to issued tokens.\n const presented = authHeader.slice('Bearer '.length).trim();\n (req as Request & { auth?: AuthInfo }).auth = {\n token: presented,\n clientId: 'api-key',\n scopes: [],\n };\n next();\n return;\n }\n sendJsonRpcError(res, 401, JSONRPC_SERVER_ERROR, 'Unauthorized', null, {\n 'WWW-Authenticate': buildAuthChallenge(\n req,\n req.headers.authorization !== undefined,\n hostValidated,\n publicUrl,\n ),\n });\n };\n}\n\n/**\n * Env-derived CORS origins (hostname-form, no scheme/port — matches the SDK\n * app's `allowedOrigins` consumer). Defaults to the SDK's loopback hostname set\n * so loopback browser clients keep working; operators set\n * `FS_ALLOWED_ORIGINS` to allow remote clients on non-loopback\n * binds. An empty value reads as unset, matching how parseAllowedHostsEnv\n * treats an all-empty list. The same set is consulted by corsPreflightHandler\n * so a remote origin is reflected end-to-end in Access-Control-Allow-Origin.\n */\nexport function computeAllowedOriginHostnames(originsEnv: string | undefined): string[] {\n // Truthiness, not list length: an all-blank value (\" , \") is a configured but\n // empty allow-list — deny every remote origin — while an unset or \"\" value\n // falls back to the loopback defaults.\n return originsEnv ? splitCsvList(originsEnv) : localhostAllowedHostnames();\n}\n\n/**\n * Reflect a present Origin on the response if it is allowed — localhost, or\n * in the env-derived `FS_ALLOWED_ORIGINS` set — and avoid\n * emitting a wildcard fallback. Shared by the OPTIONS preflight and the real\n * POST response: `createMcpExpressApp`'s `allowedOrigins` only gates which\n * Origins are accepted, it never sets `Access-Control-Allow-Origin` on the\n * actual response, so without this a browser client would pass preflight and\n * then have the POST response body blocked by CORS.\n */\nfunction reflectAllowedOrigin(\n req: Request,\n res: Response,\n allowedOriginHostnames: readonly string[],\n): void {\n const origin = req.headers.origin;\n if (origin && isOriginAllowed(origin, allowedOriginHostnames)) {\n res.header('Access-Control-Allow-Origin', origin);\n // Key the response by Origin so a CDN/proxy caching one origin's response\n // cannot replay it for a different origin (cache-poison).\n res.header('Vary', 'Origin');\n }\n}\n\n/** Mounted ahead of the `/mcp` handlers so every response — not just the\n * OPTIONS preflight — carries `Access-Control-Allow-Origin` for an allowed\n * Origin. */\nexport function corsOriginMiddleware(allowedOriginHostnames: readonly string[]): RequestHandler {\n return (req: Request, res: Response, next: NextFunction): void => {\n reflectAllowedOrigin(req, res, allowedOriginHostnames);\n next();\n };\n}\n\n/**\n * OPTIONS preflight for `/mcp`. The allow-list carries the SEP-2243 standard\n * headers (`mcp-protocol-version`, `mcp-method`, `mcp-name`): SDK clients send\n * all three on every modern request POST and `createMcpHandler` requires them\n * (400 / -32020), so omitting them here would fail every browser preflight —\n * and the HTTP leg is modern-only, so there is no legacy path to fall back to.\n * `mcp-session-id` is deliberately absent: it is 2025-era only and this\n * endpoint rejects legacy traffic.\n */\nexport function corsPreflightHandler(allowedOriginHostnames: readonly string[]): RequestHandler {\n return (req: Request, res: Response): void => {\n reflectAllowedOrigin(req, res, allowedOriginHostnames);\n res.header('Access-Control-Allow-Methods', 'POST, OPTIONS');\n res.header(\n 'Access-Control-Allow-Headers',\n 'Content-Type, Authorization, mcp-protocol-version, mcp-method, mcp-name',\n );\n res.status(204).end();\n };\n}\n\n// ─── Rate limiting (public bind only) ────────────────────────────────────────\n\ninterface RateBucket {\n windowStart: number;\n count: number;\n}\n\n// Fixed 60s window; never tuned, so it is a constant rather than config plumbing.\nconst RATE_LIMIT_WINDOW_MS = 60_000;\n\n/**\n * Fixed-window per-client-IP rate limiter.\n */\nexport function createRateLimiter(max: number): RequestHandler {\n const buckets = new Map<string, RateBucket>();\n const sweep = setInterval(() => {\n const cutoff = Date.now() - RATE_LIMIT_WINDOW_MS * 2;\n for (const [ip, bucket] of buckets) {\n if (bucket.windowStart < cutoff) buckets.delete(ip);\n }\n }, RATE_LIMIT_WINDOW_MS);\n sweep.unref();\n\n return (req: Request, res: Response, next: NextFunction): void => {\n const ip = req.ip ?? req.socket.remoteAddress ?? 'unknown';\n const now = Date.now();\n let bucket = buckets.get(ip);\n if (!bucket || now - bucket.windowStart >= RATE_LIMIT_WINDOW_MS) {\n bucket = { windowStart: now, count: 0 };\n buckets.set(ip, bucket);\n }\n bucket.count += 1;\n if (bucket.count <= max) {\n next();\n return;\n }\n const retryAfterSec = Math.max(\n 1,\n Math.ceil((bucket.windowStart + RATE_LIMIT_WINDOW_MS - now) / 1000),\n );\n sendJsonRpcError(res, 429, JSONRPC_SERVER_ERROR, 'Rate limit exceeded', null, {\n 'Retry-After': String(retryAfterSec),\n });\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"http-policy.js","sourceRoot":"","sources":["../../src/transport/http-policy.ts"],"names":[],"mappings":"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","sourcesContent":["import { getOAuthProtectedResourceMetadataUrl } from '@modelcontextprotocol/express';\nimport { localhostAllowedHostnames } from '@modelcontextprotocol/server';\nimport type { AuthInfo } from '@modelcontextprotocol/server';\n\nimport { createHash, timingSafeEqual } from 'node:crypto';\n\nimport type { NextFunction, Request, RequestHandler, Response } from 'express';\n\nimport { ErrorCode, FsError } from '../core/errors.js';\nimport { Logger } from '../core/observability.js';\nimport { splitCsvList } from '../core/util.js';\nimport { jsonRpcError } from './shared.js';\n\nconst MAX_BEARER_TOKEN_LENGTH = 4096;\n\n/**\n * JSON-RPC's server-defined error range. No SDK enum maps to \"Unauthorized\" or\n * \"Rate limit exceeded\", both of which this module refuses before any handler\n * has parsed a request.\n */\nexport const JSONRPC_SERVER_ERROR = -32000;\n\n/**\n * The one way an HTTP refusal goes out — this module's pre-handler rejections\n * and `http.ts`'s route-level ones. The envelope itself is `jsonRpcError` in\n * `shared.ts`, so the stdio leg sends the same literal; this wrapper owns only\n * what HTTP adds on top.\n *\n * `headers` carries what a particular refusal owes the client on top of the\n * JSON content type — `WWW-Authenticate` on a 401, `Retry-After` on a 429.\n * Goes out through `res.json` so every refusal keeps the `charset=utf-8` and\n * `Content-Length` a hand-rolled `writeHead`/`end` pair drops.\n */\nexport function sendJsonRpcError(\n res: Response,\n status: number,\n code: number,\n message: string,\n id: string | number | null = null,\n headers: Record<string, string> = {},\n): void {\n res\n .status(status)\n .set(headers)\n .json(jsonRpcError(code, message, id));\n}\n\nconst LOCALHOST_ORIGIN_RE = /^https?:\\/\\/(localhost|127\\.0\\.0\\.1|\\[::1\\])(:\\d+)?$/u;\n\n/**\n * Pure HTTP auth and binding policy. Holds no state; all functions are\n * directly testable without spinning up a server.\n */\nexport function isLoopbackHttpHost(host: string): boolean {\n const normalizedHost = host.trim().toLowerCase();\n return (\n normalizedHost === '127.0.0.1' ||\n normalizedHost === 'localhost' ||\n normalizedHost === '[::1]' ||\n normalizedHost === '::1'\n );\n}\n\nexport function isAllowedLocalhostOrigin(origin: string): boolean {\n return LOCALHOST_ORIGIN_RE.test(origin);\n}\n\n/** A bind to every interface. Clients never send `Host: 0.0.0.0`, so this bind\n * cannot derive its allowed-host set from itself. */\nfunction isWildcardHttpHost(host: string): boolean {\n return host === '0.0.0.0' || host === '::';\n}\n\nfunction originHostname(origin: string): string | undefined {\n try {\n return new URL(origin).hostname;\n } catch {\n return undefined;\n }\n}\n\n/**\n * True if `origin` (a raw `Origin` request header) is allowed given the\n * env-derived `allowedHostnames` set (hostname-form, no scheme/port). Localhost\n * origins are always accepted via {@link isAllowedLocalhostOrigin}; a remote\n * origin is accepted iff its parsed hostname is in the set. Both the SDK app's\n * `allowedOrigins` and this OPTIONS-handler check consume hostname-form, so a\n * remote origin allowed via `FS_ALLOWED_ORIGINS` is reflected\n * end-to-end in `Access-Control-Allow-Origin`.\n */\nexport function isOriginAllowed(origin: string, allowedHostnames: readonly string[]): boolean {\n if (isAllowedLocalhostOrigin(origin)) return true;\n const host = originHostname(origin);\n return host !== undefined && allowedHostnames.includes(host);\n}\n\nexport function validateBearerAuthorization(\n apiKey: string,\n authHeader: unknown,\n): authHeader is string {\n const bearerPrefix = 'Bearer ';\n if (typeof authHeader !== 'string' || !authHeader.startsWith(bearerPrefix)) {\n return false;\n }\n const userKey = authHeader.slice(bearerPrefix.length).trim();\n const normalizedApiKey = apiKey.trim();\n if (userKey.length > MAX_BEARER_TOKEN_LENGTH || normalizedApiKey.length === 0) {\n return false;\n }\n\n // Pure: hash per call. createHash is negligible next to the timingSafeEqual\n // already done per request, and avoiding module-level cache state keeps this\n // testable without post-import env-mutation footguns.\n const expectedHash = createHash('sha256').update(normalizedApiKey).digest();\n const actualHash = createHash('sha256').update(userKey).digest();\n return timingSafeEqual(expectedHash, actualHash);\n}\n\nfunction isSecureApiKey(key: string | undefined): boolean {\n return typeof key === 'string' && key.trim().length >= 16;\n}\n\n/**\n * Refuse to bind to a non-loopback host without an API key. Throws on\n * policy violation; returns silently when allowed.\n */\nexport function assertHttpBindingPolicy(host: string, apiKey: string | undefined): void {\n if (isLoopbackHttpHost(host)) {\n if (apiKey !== undefined && !isSecureApiKey(apiKey)) {\n throw new FsError(\n ErrorCode.PERMISSION_DENIED,\n 'API_KEY is configured but is insecure (minimum 16 characters).',\n );\n }\n return;\n }\n if (isSecureApiKey(apiKey)) return;\n throw new FsError(\n ErrorCode.PERMISSION_DENIED,\n `Refusing to bind HTTP server to non-loopback host '${host}' without a secure API_KEY (minimum 16 characters).`,\n );\n}\n\n/**\n * The Host header values this bind accepts. `FS_ALLOWED_HOSTS` wins\n * when set; otherwise a loopback bind takes the whole localhost hostname set (a\n * client dialing http://localhost:<port> sends `Host: localhost`, which a bare\n * ['127.0.0.1'] list would 403), a wildcard bind takes none, and a concrete\n * non-loopback bind takes itself. An empty result means no Host validation is\n * mounted — only reachable under FS_ALLOW_UNRESTRICTED_HOSTS=1.\n */\nexport function resolveAllowedHosts(\n httpHost: string,\n allowedHostsEnv: string | undefined,\n): string[] {\n const configured = splitCsvList(allowedHostsEnv);\n if (configured.length > 0) return configured;\n if (isLoopbackHttpHost(httpHost)) return localhostAllowedHostnames();\n if (isWildcardHttpHost(httpHost)) return [];\n return [httpHost];\n}\n\n/**\n * Express's `trust proxy` setting from `FS_TRUST_PROXY`. A\n * non-negative integer hop count parses to a number (Express counts hops from\n * the socket); anything else (a subnet name/expression, or a negative/\n * non-integer string) passes through unchanged for Express to interpret.\n * `undefined` means the env var was unset or empty — leave Express's default\n * (disabled) in place.\n */\nexport function resolveTrustProxySetting(value: string | undefined): number | string | undefined {\n if (!value) return undefined;\n const hops = Number(value);\n return Number.isInteger(hops) && hops >= 0 ? hops : value;\n}\n\n/**\n * Refuse to bind a wildcard host (`0.0.0.0` / `::`) without an explicit\n * `FS_ALLOWED_HOSTS` list. Clients never send `Host: 0.0.0.0`, so\n * defaulting the allowed-host set to the wildcard string would reject all real\n * traffic. Operators who accept the risk can set\n * `FS_ALLOW_UNRESTRICTED_HOSTS=1` to restore warn-and-bind.\n * Loopback and concrete non-loopback hosts are unaffected.\n */\nexport function assertHttpHostPolicy(\n host: string,\n allowedHosts: readonly string[],\n allowUnrestricted: boolean,\n): void {\n if (isLoopbackHttpHost(host)) return;\n // concrete non-loopback: Host validated against the bind host.\n if (!isWildcardHttpHost(host)) return;\n if (allowUnrestricted) return;\n if (allowedHosts.length > 0) return;\n throw new FsError(\n ErrorCode.PERMISSION_DENIED,\n `Refusing to bind wildcard host '${host}' without FS_ALLOWED_HOSTS. Set it to the public hostname(s) clients send, or set FS_ALLOW_UNRESTRICTED_HOSTS=true to accept the risk.`,\n );\n}\n\n// ─── Protected-resource discovery (RFC 9728 / RFC 6750) ──────────────────────\n\n/**\n * This server is a resource server with no authorization server: `API_KEY` is a\n * static secret the operator hands out of band, not an issued token. So the\n * metadata document deliberately omits `authorization_servers` — RFC 9728 §2\n * makes it optional, and its absence is the accurate statement that a token\n * cannot be obtained from an endpoint. `mcpAuthMetadataRouter` from\n * `@modelcontextprotocol/express` is the tool for the IdP-backed case: it\n * requires RFC 8414 authorization-server metadata, and inventing an issuer with\n * endpoints that answer nothing would send clients into a flow that cannot\n * complete. Adopt it if this ever moves to a real IdP.\n *\n * What discovery buys here: a client hitting 401 learns the resource identifier\n * and that the credential goes in the Authorization header, instead of a bare\n * challenge plus a 404 on the well-known path.\n */\nexport function protectedResourceUrl(\n req: Request,\n hostValidated: boolean,\n configured: string | undefined,\n): URL | null {\n if (configured) {\n const parsed = URL.parse(configured);\n if (parsed) return parsed;\n Logger.warn(\n `[HTTP] Ignoring unparseable FS_PUBLIC_URL: ${configured}. Deriving the resource identifier from the Host header instead.`,\n );\n }\n // With no Host validator mounted (FS_ALLOW_UNRESTRICTED_HOSTS=1)\n // the Host header is attacker-controlled, and naming a resource from it would\n // publish an identifier the operator does not own. Answer with nothing instead.\n if (!hostValidated) return null;\n const host = req.headers.host ?? '127.0.0.1';\n return URL.parse(`${req.secure ? 'https' : 'http'}://${host}/mcp`);\n}\n\n/**\n * RFC 6750 §3: a request with no credentials gets a bare challenge, while one\n * that presented something invalid gets `error=\"invalid_token\"`. Clients use\n * the difference to tell \"you must authenticate\" from \"your token is wrong\".\n */\nfunction buildAuthChallenge(\n req: Request,\n hasCredentials: boolean,\n hostValidated: boolean,\n publicUrl: string | undefined,\n): string {\n const resource = protectedResourceUrl(req, hostValidated, publicUrl);\n const params: string[] = [];\n if (resource) {\n params.push(`resource_metadata=\"${getOAuthProtectedResourceMetadataUrl(resource)}\"`);\n }\n if (hasCredentials) {\n params.push('error=\"invalid_token\"', 'error_description=\"Invalid or expired bearer token\"');\n }\n // A bare `Bearer` is a complete challenge; avoid emitting a trailing space.\n return params.length > 0 ? `Bearer ${params.join(', ')}` : 'Bearer';\n}\n\n/**\n * Express middleware: when `apiKey` is set, require a matching bearer token.\n * No key set = open access (loopback dev mode). `apiKey` is captured once per\n * app setup (passed in from startHttpServer) so the middleware and\n * assertHttpBindingPolicy share one source of truth.\n *\n * `requireBearerAuth` + `verifyAccessToken` from `@modelcontextprotocol/express`\n * is the tool for verifier-backed tokens; this middleware stays hand-rolled\n * because the credential is a static operator key with no verifier, the 401 body\n * must stay a JSON-RPC envelope, and the RFC 6750 bare-vs-`invalid_token`\n * challenge split is implemented here. Adopt `requireBearerAuth` if this ever\n * verifies issued tokens.\n */\nexport function bearerAuthMiddleware(\n apiKey: string | undefined,\n hostValidated: boolean,\n publicUrl?: string,\n): RequestHandler {\n return (req: Request, res: Response, next: NextFunction): void => {\n if (!apiKey) {\n next();\n return;\n }\n const authHeader = req.headers.authorization;\n if (isSecureApiKey(apiKey) && validateBearerAuthorization(apiKey, authHeader)) {\n // Forward the validated caller to the SDK pipeline: toNodeHandler reads\n // req.auth and passes it as the handler's pass-through authInfo, which the\n // per-request factory receives on ctx.http.\n // `expiresAt` is deliberately omitted: a static API key has no expiry and\n // there is no `exp` claim or introspection response to read one from.\n // Populate it from the token if this ever moves to issued tokens.\n const presented = authHeader.slice('Bearer '.length).trim();\n (req as Request & { auth?: AuthInfo }).auth = {\n token: presented,\n clientId: 'api-key',\n scopes: [],\n };\n next();\n return;\n }\n sendJsonRpcError(res, 401, JSONRPC_SERVER_ERROR, 'Unauthorized', null, {\n 'WWW-Authenticate': buildAuthChallenge(\n req,\n req.headers.authorization !== undefined,\n hostValidated,\n publicUrl,\n ),\n });\n };\n}\n\n/**\n * Env-derived CORS origins (hostname-form, no scheme/port — matches the SDK\n * app's `allowedOrigins` consumer). Defaults to the SDK's loopback hostname set\n * so loopback browser clients keep working; operators set\n * `FS_ALLOWED_ORIGINS` to allow remote clients on non-loopback\n * binds. An empty value reads as unset, matching how parseAllowedHostsEnv\n * treats an all-empty list. The same set is consulted by corsPreflightHandler\n * so a remote origin is reflected end-to-end in Access-Control-Allow-Origin.\n */\nexport function computeAllowedOriginHostnames(originsEnv: string | undefined): string[] {\n // Truthiness, not list length: an all-blank value (\" , \") is a configured but\n // empty allow-list — deny every remote origin — while an unset or \"\" value\n // falls back to the loopback defaults.\n return originsEnv ? splitCsvList(originsEnv) : localhostAllowedHostnames();\n}\n\n/**\n * Reflect a present Origin on the response if it is allowed — localhost, or\n * in the env-derived `FS_ALLOWED_ORIGINS` set — and avoid\n * emitting a wildcard fallback. Shared by the OPTIONS preflight and the real\n * POST response: `createMcpExpressApp`'s `allowedOrigins` only gates which\n * Origins are accepted, it never sets `Access-Control-Allow-Origin` on the\n * actual response, so without this a browser client would pass preflight and\n * then have the POST response body blocked by CORS.\n */\nfunction reflectAllowedOrigin(\n req: Request,\n res: Response,\n allowedOriginHostnames: readonly string[],\n): void {\n const origin = req.headers.origin;\n if (origin && isOriginAllowed(origin, allowedOriginHostnames)) {\n res.header('Access-Control-Allow-Origin', origin);\n // Key the response by Origin so a CDN/proxy caching one origin's response\n // cannot replay it for a different origin (cache-poison).\n res.header('Vary', 'Origin');\n }\n}\n\n/** Mounted ahead of the `/mcp` handlers so every response — not just the\n * OPTIONS preflight — carries `Access-Control-Allow-Origin` for an allowed\n * Origin. */\nexport function corsOriginMiddleware(allowedOriginHostnames: readonly string[]): RequestHandler {\n return (req: Request, res: Response, next: NextFunction): void => {\n reflectAllowedOrigin(req, res, allowedOriginHostnames);\n next();\n };\n}\n\n/**\n * OPTIONS preflight for `/mcp`. The allow-list carries the SEP-2243 standard\n * headers (`mcp-protocol-version`, `mcp-method`, `mcp-name`): SDK clients send\n * all three on every modern request POST and `createMcpHandler` requires them\n * (400 / -32020), so omitting them here would fail every browser preflight —\n * and the HTTP leg is modern-only, so there is no legacy path to fall back to.\n * `mcp-session-id` is deliberately absent: it is 2025-era only and this\n * endpoint rejects legacy traffic.\n */\nexport function corsPreflightHandler(allowedOriginHostnames: readonly string[]): RequestHandler {\n return (req: Request, res: Response): void => {\n reflectAllowedOrigin(req, res, allowedOriginHostnames);\n res.header('Access-Control-Allow-Methods', 'POST, OPTIONS');\n res.header(\n 'Access-Control-Allow-Headers',\n 'Content-Type, Authorization, mcp-protocol-version, mcp-method, mcp-name',\n );\n res.status(204).end();\n };\n}\n\n// ─── Rate limiting (public bind only) ────────────────────────────────────────\n\ninterface RateBucket {\n windowStart: number;\n count: number;\n}\n\n// Fixed 60s window; never tuned, so it is a constant rather than config plumbing.\nconst RATE_LIMIT_WINDOW_MS = 60_000;\n\n/**\n * Fixed-window per-client-IP rate limiter.\n */\nexport function createRateLimiter(max: number): RequestHandler {\n const buckets = new Map<string, RateBucket>();\n const sweep = setInterval(() => {\n const cutoff = Date.now() - RATE_LIMIT_WINDOW_MS * 2;\n for (const [ip, bucket] of buckets) {\n if (bucket.windowStart < cutoff) buckets.delete(ip);\n }\n }, RATE_LIMIT_WINDOW_MS);\n sweep.unref();\n\n return (req: Request, res: Response, next: NextFunction): void => {\n const ip = req.ip ?? req.socket.remoteAddress ?? 'unknown';\n const now = Date.now();\n let bucket = buckets.get(ip);\n if (!bucket || now - bucket.windowStart >= RATE_LIMIT_WINDOW_MS) {\n bucket = { windowStart: now, count: 0 };\n buckets.set(ip, bucket);\n }\n bucket.count += 1;\n if (bucket.count <= max) {\n next();\n return;\n }\n const retryAfterSec = Math.max(\n 1,\n Math.ceil((bucket.windowStart + RATE_LIMIT_WINDOW_MS - now) / 1000),\n );\n sendJsonRpcError(res, 429, JSONRPC_SERVER_ERROR, 'Rate limit exceeded', null, {\n 'Retry-After': String(retryAfterSec),\n });\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/transport/http.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AASxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/transport/http.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AASxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAwBrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAgPjD,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,EACtB,MAAM,GAAE,aAAkB,GACzB,OAAO,CAAC,MAAM,CAAC,CAuHjB"}
|
package/dist/transport/http.js
CHANGED
|
@@ -13,16 +13,9 @@ import { ResourceStore } from '../core/store.js';
|
|
|
13
13
|
import { MIB, parseEnvInt } from '../core/util.js';
|
|
14
14
|
import { createWatcherRegistry, MAX_WATCHERS, } from '../core/watcher-registry.js';
|
|
15
15
|
import { createServer } from '../server.js';
|
|
16
|
-
import { assertHttpBindingPolicy, assertHttpHostPolicy, bearerAuthMiddleware, computeAllowedOriginHostnames, corsOriginMiddleware, corsPreflightHandler, createRateLimiter, protectedResourceUrl, resolveAllowedHosts, resolveTrustProxySetting, sendJsonRpcError, } from './http-policy.js';
|
|
16
|
+
import { assertHttpBindingPolicy, assertHttpHostPolicy, bearerAuthMiddleware, computeAllowedOriginHostnames, corsOriginMiddleware, corsPreflightHandler, createRateLimiter, JSONRPC_SERVER_ERROR, protectedResourceUrl, resolveAllowedHosts, resolveTrustProxySetting, sendJsonRpcError, } from './http-policy.js';
|
|
17
17
|
import { isStructurallyValidListen, jsonRpcRequestId, listenSubscriptionUris, prepareListenWatchers, } from './shared.js';
|
|
18
18
|
const MAX_REQUEST_BODY_BYTES = parseEnvInt('FS_MAX_REQUEST_BYTES', 4 * MIB, 1024, 256 * MIB);
|
|
19
|
-
/**
|
|
20
|
-
* The code the SDK's own non-POST rejection carries. Not a `ProtocolErrorCode`
|
|
21
|
-
* member — the enum has no `-32000` — so it is pinned here to keep the local
|
|
22
|
-
* 405 envelope identical to the one the handler would have produced.
|
|
23
|
-
*/
|
|
24
|
-
const SDK_METHOD_NOT_ALLOWED_CODE = -32000;
|
|
25
|
-
const SDK_UNSUPPORTED_MEDIA_TYPE_CODE = -32000;
|
|
26
19
|
/** Body-parser rejections and anything else that reaches the end of the chain. */
|
|
27
20
|
function errorHandlerMiddleware(err, _req, res, next) {
|
|
28
21
|
if (res.headersSent) {
|
|
@@ -116,7 +109,7 @@ function setupExpressApp(httpHost, apiKey, allowedHosts, modernNodeHandler, shar
|
|
|
116
109
|
app.post('/mcp', (req, res, next) => {
|
|
117
110
|
void (async () => {
|
|
118
111
|
if (!isJsonContentType(req.headers['content-type'])) {
|
|
119
|
-
sendJsonRpcError(res, 415,
|
|
112
|
+
sendJsonRpcError(res, 415, JSONRPC_SERVER_ERROR, 'Unsupported Media Type: Content-Type must be application/json');
|
|
120
113
|
return;
|
|
121
114
|
}
|
|
122
115
|
const parsedBody = req.body;
|
|
@@ -183,7 +176,7 @@ function setupExpressApp(httpHost, apiKey, allowedHosts, modernNodeHandler, shar
|
|
|
183
176
|
// header RFC 9110 §15.5.6 requires and routes every routine GET probe through
|
|
184
177
|
// the handler's `onerror` — which this server logs at error level.
|
|
185
178
|
app.all('/mcp', (_req, res) => {
|
|
186
|
-
sendJsonRpcError(res, 405,
|
|
179
|
+
sendJsonRpcError(res, 405, JSONRPC_SERVER_ERROR, 'Method not allowed.', null, {
|
|
187
180
|
Allow: 'POST, OPTIONS',
|
|
188
181
|
});
|
|
189
182
|
});
|
|
@@ -216,7 +209,7 @@ export async function startHttpServer(port, options, config = {}) {
|
|
|
216
209
|
// every caller presents the same key (one auth context by construction), and
|
|
217
210
|
// without it the bind is loopback-only. Split into per-auth-context guards if
|
|
218
211
|
// this ever serves more than one credential.
|
|
219
|
-
const sharedPathGuard = new PathGuard(options
|
|
212
|
+
const sharedPathGuard = new PathGuard(options);
|
|
220
213
|
await sharedPathGuard.recomputeAllowedDirectories();
|
|
221
214
|
const modernHandler = createMcpHandler(makeHttpModernFactory(options, () => modernHandler.notify, sharedRegistry, sharedPathGuard, sharedStore, sharedPageStore, apiKey), {
|
|
222
215
|
legacy: 'reject',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/transport/http.ts"],"names":[],"mappings":"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","sourcesContent":["// HTTP hosting: express app assembly, security middleware wiring, the modern\n// per-request server factory, and listen-filter watcher gating on POST /mcp.\nimport { createMcpExpressApp } from '@modelcontextprotocol/express';\nimport { toNodeHandler } from '@modelcontextprotocol/node';\nimport type {\n McpHttpHandler,\n McpServerFactory,\n ServerNotifier,\n} from '@modelcontextprotocol/server';\nimport {\n createMcpHandler,\n DEFAULT_REQUEST_TIMEOUT_MSEC,\n isJsonContentType,\n ProtocolErrorCode,\n} from '@modelcontextprotocol/server';\n\nimport type { Server } from 'node:http';\nimport { createServer as createHttpServer } from 'node:http';\n\nimport type { Express, NextFunction, Request, Response } from 'express';\n\nimport { formatUnknownErrorMessage } from '../core/errors.js';\nimport { Logger } from '../core/observability.js';\nimport { PageSnapshotStore } from '../core/page-store.js';\nimport { PathGuard } from '../core/path.js';\nimport type { ServerOptions } from '../core/path.js';\nimport { parseTrueEnvFlag } from '../core/primitives.js';\nimport { ResourceStore } from '../core/store.js';\nimport { MIB, parseEnvInt } from '../core/util.js';\nimport {\n createWatcherRegistry,\n MAX_WATCHERS,\n type WatcherRegistry,\n} from '../core/watcher-registry.js';\nimport { createServer } from '../server.js';\nimport {\n assertHttpBindingPolicy,\n assertHttpHostPolicy,\n bearerAuthMiddleware,\n computeAllowedOriginHostnames,\n corsOriginMiddleware,\n corsPreflightHandler,\n createRateLimiter,\n protectedResourceUrl,\n resolveAllowedHosts,\n resolveTrustProxySetting,\n sendJsonRpcError,\n} from './http-policy.js';\nimport type { RuntimeConfig } from './shared.js';\nimport {\n isStructurallyValidListen,\n jsonRpcRequestId,\n listenSubscriptionUris,\n prepareListenWatchers,\n} from './shared.js';\n\nconst MAX_REQUEST_BODY_BYTES = parseEnvInt('FS_MAX_REQUEST_BYTES', 4 * MIB, 1024, 256 * MIB);\n\n/**\n * The code the SDK's own non-POST rejection carries. Not a `ProtocolErrorCode`\n * member — the enum has no `-32000` — so it is pinned here to keep the local\n * 405 envelope identical to the one the handler would have produced.\n */\nconst SDK_METHOD_NOT_ALLOWED_CODE = -32000;\nconst SDK_UNSUPPORTED_MEDIA_TYPE_CODE = -32000;\n\n/** Body-parser rejections and anything else that reaches the end of the chain. */\nfunction errorHandlerMiddleware(\n err: Error & { status?: number },\n _req: Request,\n res: Response,\n next: NextFunction,\n): void {\n if (res.headersSent) {\n next(err);\n return;\n }\n if (err.status === 413) {\n sendJsonRpcError(res, 413, ProtocolErrorCode.InvalidRequest, 'Request body too large');\n return;\n }\n if (err.status === 400) {\n sendJsonRpcError(res, 400, ProtocolErrorCode.ParseError, 'Invalid JSON in request body');\n return;\n }\n Logger.error('[HTTP] Unhandled middleware error:', formatUnknownErrorMessage(err));\n sendJsonRpcError(res, 500, ProtocolErrorCode.InternalError, 'Internal Server Error');\n}\n\nfunction makeHttpModernFactory(\n options: ServerOptions,\n getNotifier: () => ServerNotifier,\n sharedRegistry: WatcherRegistry,\n sharedPathGuard: PathGuard,\n sharedStore: ResourceStore,\n sharedPageStore: PageSnapshotStore,\n apiKey: string | undefined,\n): McpServerFactory {\n return async ({ era }) => {\n const notifier = getNotifier();\n const c = await createServer(options, {\n watcherRegistry: sharedRegistry,\n notifier,\n pathGuard: sharedPathGuard,\n resourceStore: sharedStore,\n pageStore: sharedPageStore,\n era,\n ...(apiKey !== undefined ? { apiKey } : {}),\n });\n const previousOnClose = c.mcp.server.onclose;\n c.mcp.server.onclose = () => {\n previousOnClose?.();\n c.disposeRuntimeState();\n };\n return c.mcp;\n };\n}\n\nfunction setupExpressApp(\n httpHost: string,\n apiKey: string | undefined,\n allowedHosts: string[],\n modernNodeHandler: (req: Request, res: Response, parsedBody?: unknown) => Promise<void>,\n sharedPathGuard: PathGuard,\n sharedRegistry: WatcherRegistry,\n notifier: ServerNotifier,\n): Express {\n const allowedOriginHostnames = computeAllowedOriginHostnames(process.env['FS_ALLOWED_ORIGINS']);\n // Read once here and passed down, like every other env-derived policy input:\n // http-policy holds no state and reads no env of its own.\n const publicUrl = process.env['FS_PUBLIC_URL'];\n\n const app = createMcpExpressApp({\n host: httpHost,\n jsonLimit: `${MAX_REQUEST_BODY_BYTES}b`,\n ...(allowedHosts.length > 0 ? { allowedHosts: [...allowedHosts] } : {}),\n ...(allowedOriginHostnames.length > 0 ? { allowedOrigins: [...allowedOriginHostnames] } : {}),\n });\n\n const trustProxy = resolveTrustProxySetting(process.env['FS_TRUST_PROXY']);\n if (trustProxy !== undefined) {\n app.set('trust proxy', trustProxy);\n }\n\n if (allowedHosts.length === 0) {\n Logger.warn(\n '[HTTP] FS_ALLOW_UNRESTRICTED_HOSTS is set: binding globally without Host validation.',\n );\n }\n\n app.options('/mcp', corsPreflightHandler(allowedOriginHostnames));\n app.use('/mcp', corsOriginMiddleware(allowedOriginHostnames));\n\n // Unconditional: the spec's rate-limit MUST is not scoped to authenticated\n // binds. A keyless bind is loopback-only, so the cap is looser, not absent.\n const rpm = parseEnvInt('FS_RATE_LIMIT_RPM', apiKey ? 120 : 6_000, 1, 100_000);\n app.use('/mcp', createRateLimiter(rpm));\n\n if (apiKey && allowedHosts.length > 0) {\n const metadataHandler = (req: Request, res: Response): void => {\n const resource = protectedResourceUrl(req, true, publicUrl);\n if (!resource) {\n // Unreachable for the unrestricted case (the gate above excludes it),\n // but defend a FS_PUBLIC_URL parse failure with a bodyless\n // 404 — a generic OAuth client cannot parse a JSON-RPC error envelope.\n res.status(404).end();\n return;\n }\n res.header('Access-Control-Allow-Origin', '*');\n res.status(200).json({\n resource: resource.href,\n bearer_methods_supported: ['header'],\n resource_name: 'filesystem-mcp',\n });\n };\n app.get(\n ['/.well-known/oauth-protected-resource', '/.well-known/oauth-protected-resource/mcp'],\n metadataHandler,\n );\n }\n\n app.get('/healthz', (_req: Request, res: Response) => {\n res.status(200).json({\n status: 'ok',\n uptime: process.uptime(),\n });\n });\n\n app.use('/mcp', bearerAuthMiddleware(apiKey, allowedHosts.length > 0, publicUrl));\n\n const resourceUpdateSink = (uri: string): void => {\n notifier.resourceUpdated(uri);\n };\n\n app.post('/mcp', (req: Request, res: Response, next: NextFunction) => {\n void (async () => {\n if (!isJsonContentType(req.headers['content-type'])) {\n sendJsonRpcError(\n res,\n 415,\n SDK_UNSUPPORTED_MEDIA_TYPE_CODE,\n 'Unsupported Media Type: Content-Type must be application/json',\n );\n return;\n }\n const parsedBody = req.body as unknown;\n if (parsedBody === undefined) {\n // Undefined tells the Node adapter to read the raw stream without our parser's limit.\n sendJsonRpcError(res, 400, ProtocolErrorCode.ParseError, 'Invalid JSON in request body');\n return;\n }\n // Only a structurally valid listen gets watchers, mirroring the stdio\n // gate. A malformed one cannot succeed downstream, so attaching handles\n // for it only gives the response-close release something to undo — and\n // everything that is not a listen takes no watchers either way.\n if (!isStructurallyValidListen(parsedBody)) {\n await modernNodeHandler(req, res, parsedBody);\n return;\n }\n\n // Reject an over-cap listen before the ack so the client does not believe\n // every requested URI is watched when the watcher budget is exhausted.\n // The per-URI `capped` failure below would also reject, but only after\n // creating and tearing down every watcher up to the cap.\n const requestedUris = listenSubscriptionUris(parsedBody);\n // Only URIs without a live watcher consume a slot; a re-listen to an\n // already-watched URI just retains another lease.\n const newUris = requestedUris.filter((uri) => !sharedRegistry.hasWatcher(uri));\n const available = MAX_WATCHERS - sharedRegistry.size();\n if (newUris.length > available) {\n sendJsonRpcError(\n res,\n 400,\n ProtocolErrorCode.InvalidParams,\n `subscriptions/listen names ${newUris.length} not-yet-watched URIs but only ${available} watcher slots remain (cap ${MAX_WATCHERS}). Reduce the resourceSubscriptions list.`,\n jsonRpcRequestId(parsedBody),\n );\n return;\n }\n\n const prepared = await prepareListenWatchers(\n parsedBody,\n sharedPathGuard,\n sharedRegistry,\n resourceUpdateSink,\n );\n if (!prepared.ok) {\n sendJsonRpcError(\n res,\n 400,\n ProtocolErrorCode.InvalidParams,\n prepared.message,\n jsonRpcRequestId(parsedBody),\n );\n return;\n }\n\n let released = false;\n const release = (): void => {\n if (released) return;\n released = true;\n for (const uri of prepared.acquiredUris) sharedRegistry.release(uri);\n };\n if (prepared.acquiredUris.length > 0) {\n // The attach loop awaits per-URI path validation, so the client can be\n // gone by the time it returns. `close` fires once: a listener attached\n // after it would never run and every lease this batch took would leak\n // until process shutdown.\n if (res.closed || res.destroyed || res.writableEnded) release();\n else res.once('close', release);\n }\n\n try {\n await modernNodeHandler(req, res, parsedBody);\n } catch (error) {\n release();\n throw error;\n }\n })().catch(next);\n });\n\n // Answer non-POST here rather than handing it to `modernNodeHandler`. The\n // SDK's own 405 is byte-identical to this one, but it omits the `Allow`\n // header RFC 9110 §15.5.6 requires and routes every routine GET probe through\n // the handler's `onerror` — which this server logs at error level.\n app.all('/mcp', (_req: Request, res: Response) => {\n sendJsonRpcError(res, 405, SDK_METHOD_NOT_ALLOWED_CODE, 'Method not allowed.', null, {\n Allow: 'POST, OPTIONS',\n });\n });\n\n app.use(errorHandlerMiddleware);\n\n return app;\n}\n\nexport async function startHttpServer(\n port: number,\n options: ServerOptions,\n config: RuntimeConfig = {},\n): Promise<Server> {\n const httpHost = config.httpHost ?? '127.0.0.1';\n const { apiKey } = config;\n assertHttpBindingPolicy(httpHost, apiKey);\n const allowedHosts = resolveAllowedHosts(httpHost, process.env['FS_ALLOWED_HOSTS']);\n assertHttpHostPolicy(\n httpHost,\n allowedHosts,\n parseTrueEnvFlag(process.env['FS_ALLOW_UNRESTRICTED_HOSTS'], 'FS_ALLOW_UNRESTRICTED_HOSTS'),\n );\n\n const sharedRegistry = createWatcherRegistry();\n // One store for the whole endpoint, same lifetime and same one-credential\n // trust argument as the shared guard below: a result a tool externalized in\n // one POST must survive to the follow-up resources/read, and the modern leg\n // builds a fresh McpServer per request so a per-request store would discard\n // it immediately.\n // Fires only from a tool call, which is long after `modernHandler` below is\n // constructed, so reading it from the closure is safe (same as `getNotifier`).\n const sharedStore = new ResourceStore(() => {\n modernHandler.notify.resourcesChanged();\n });\n const sharedPageStore = new PageSnapshotStore();\n // One guard for the whole endpoint. The modern leg builds a fresh McpServer\n // per request, so a per-instance guard would discard every accepted access\n // grant the moment the request ended — re-prompting on each subsequent call\n // and leaving the listen-watcher path validating against a stale allowed set.\n // Grant scope is therefore the endpoint, not the connection: with API_KEY set\n // every caller presents the same key (one auth context by construction), and\n // without it the bind is loopback-only. Split into per-auth-context guards if\n // this ever serves more than one credential.\n const sharedPathGuard = new PathGuard(options, true);\n await sharedPathGuard.recomputeAllowedDirectories();\n\n const modernHandler: McpHttpHandler = createMcpHandler(\n makeHttpModernFactory(\n options,\n () => modernHandler.notify,\n sharedRegistry,\n sharedPathGuard,\n sharedStore,\n sharedPageStore,\n apiKey,\n ),\n {\n legacy: 'reject',\n onerror: (error: Error) => {\n Logger.error('[HTTP] modern leg error:', formatUnknownErrorMessage(error));\n },\n },\n );\n const modernNodeHandler = toNodeHandler(modernHandler);\n\n const app = setupExpressApp(\n httpHost,\n apiKey,\n allowedHosts,\n modernNodeHandler,\n sharedPathGuard,\n sharedRegistry,\n modernHandler.notify,\n );\n\n const httpServer = createHttpServer(app);\n // Must exceed the idle timeout of any proxy in front of this server, or the\n // proxy reuses connections the server already closed (intermittent 502s).\n const keepAliveMs = parseEnvInt('FS_KEEPALIVE_TIMEOUT_MS', 5_000, 1_000, 600_000);\n httpServer.keepAliveTimeout = keepAliveMs;\n httpServer.headersTimeout = keepAliveMs + 5_000;\n httpServer.requestTimeout = DEFAULT_REQUEST_TIMEOUT_MSEC;\n\n const onHttpServerError = (error: Error): void => {\n Logger.error('[HTTP] runtime server error', {\n host: httpHost,\n port,\n error: formatUnknownErrorMessage(error),\n });\n };\n httpServer.on('error', onHttpServerError);\n\n const originalClose = httpServer.close.bind(httpServer);\n httpServer.close = function (callback?: (error?: Error) => void) {\n sharedRegistry.destroy();\n sharedPageStore.clear();\n modernHandler\n .close()\n .then(() => {\n originalClose(callback);\n })\n .catch((err: unknown) => {\n Logger.error(\n '[HTTP] Error closing handler during HTTP server close:',\n formatUnknownErrorMessage(err),\n );\n originalClose(callback);\n });\n return httpServer;\n };\n\n return new Promise((resolve, reject) => {\n const onError = (err: Error) => {\n sharedRegistry.destroy();\n sharedPageStore.clear();\n modernHandler.close().catch((closeErr: unknown) => {\n Logger.error(\n '[HTTP] Error closing handler on startup failure:',\n formatUnknownErrorMessage(closeErr),\n );\n });\n reject(err);\n };\n httpServer.once('error', onError);\n\n httpServer.listen(port, httpHost, () => {\n httpServer.removeListener('error', onError);\n Logger.info(`[HTTP] Server listening on http://${httpHost}:${port}`);\n resolve(httpServer);\n });\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/transport/http.ts"],"names":[],"mappings":"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","sourcesContent":["// HTTP hosting: express app assembly, security middleware wiring, the modern\n// per-request server factory, and listen-filter watcher gating on POST /mcp.\nimport { createMcpExpressApp } from '@modelcontextprotocol/express';\nimport { toNodeHandler } from '@modelcontextprotocol/node';\nimport type {\n McpHttpHandler,\n McpServerFactory,\n ServerNotifier,\n} from '@modelcontextprotocol/server';\nimport {\n createMcpHandler,\n DEFAULT_REQUEST_TIMEOUT_MSEC,\n isJsonContentType,\n ProtocolErrorCode,\n} from '@modelcontextprotocol/server';\n\nimport type { Server } from 'node:http';\nimport { createServer as createHttpServer } from 'node:http';\n\nimport type { Express, NextFunction, Request, Response } from 'express';\n\nimport { formatUnknownErrorMessage } from '../core/errors.js';\nimport { Logger } from '../core/observability.js';\nimport { PageSnapshotStore } from '../core/page-store.js';\nimport { PathGuard } from '../core/path.js';\nimport type { ServerOptions } from '../core/path.js';\nimport { parseTrueEnvFlag } from '../core/primitives.js';\nimport { ResourceStore } from '../core/store.js';\nimport { MIB, parseEnvInt } from '../core/util.js';\nimport {\n createWatcherRegistry,\n MAX_WATCHERS,\n type WatcherRegistry,\n} from '../core/watcher-registry.js';\nimport { createServer } from '../server.js';\nimport {\n assertHttpBindingPolicy,\n assertHttpHostPolicy,\n bearerAuthMiddleware,\n computeAllowedOriginHostnames,\n corsOriginMiddleware,\n corsPreflightHandler,\n createRateLimiter,\n JSONRPC_SERVER_ERROR,\n protectedResourceUrl,\n resolveAllowedHosts,\n resolveTrustProxySetting,\n sendJsonRpcError,\n} from './http-policy.js';\nimport type { RuntimeConfig } from './shared.js';\nimport {\n isStructurallyValidListen,\n jsonRpcRequestId,\n listenSubscriptionUris,\n prepareListenWatchers,\n} from './shared.js';\n\nconst MAX_REQUEST_BODY_BYTES = parseEnvInt('FS_MAX_REQUEST_BYTES', 4 * MIB, 1024, 256 * MIB);\n\n/** Body-parser rejections and anything else that reaches the end of the chain. */\nfunction errorHandlerMiddleware(\n err: Error & { status?: number },\n _req: Request,\n res: Response,\n next: NextFunction,\n): void {\n if (res.headersSent) {\n next(err);\n return;\n }\n if (err.status === 413) {\n sendJsonRpcError(res, 413, ProtocolErrorCode.InvalidRequest, 'Request body too large');\n return;\n }\n if (err.status === 400) {\n sendJsonRpcError(res, 400, ProtocolErrorCode.ParseError, 'Invalid JSON in request body');\n return;\n }\n Logger.error('[HTTP] Unhandled middleware error:', formatUnknownErrorMessage(err));\n sendJsonRpcError(res, 500, ProtocolErrorCode.InternalError, 'Internal Server Error');\n}\n\nfunction makeHttpModernFactory(\n options: ServerOptions,\n getNotifier: () => ServerNotifier,\n sharedRegistry: WatcherRegistry,\n sharedPathGuard: PathGuard,\n sharedStore: ResourceStore,\n sharedPageStore: PageSnapshotStore,\n apiKey: string | undefined,\n): McpServerFactory {\n return async ({ era }) => {\n const notifier = getNotifier();\n const c = await createServer(options, {\n watcherRegistry: sharedRegistry,\n notifier,\n pathGuard: sharedPathGuard,\n resourceStore: sharedStore,\n pageStore: sharedPageStore,\n era,\n ...(apiKey !== undefined ? { apiKey } : {}),\n });\n const previousOnClose = c.mcp.server.onclose;\n c.mcp.server.onclose = () => {\n previousOnClose?.();\n c.disposeRuntimeState();\n };\n return c.mcp;\n };\n}\n\nfunction setupExpressApp(\n httpHost: string,\n apiKey: string | undefined,\n allowedHosts: string[],\n modernNodeHandler: (req: Request, res: Response, parsedBody?: unknown) => Promise<void>,\n sharedPathGuard: PathGuard,\n sharedRegistry: WatcherRegistry,\n notifier: ServerNotifier,\n): Express {\n const allowedOriginHostnames = computeAllowedOriginHostnames(process.env['FS_ALLOWED_ORIGINS']);\n // Read once here and passed down, like every other env-derived policy input:\n // http-policy holds no state and reads no env of its own.\n const publicUrl = process.env['FS_PUBLIC_URL'];\n\n const app = createMcpExpressApp({\n host: httpHost,\n jsonLimit: `${MAX_REQUEST_BODY_BYTES}b`,\n ...(allowedHosts.length > 0 ? { allowedHosts: [...allowedHosts] } : {}),\n ...(allowedOriginHostnames.length > 0 ? { allowedOrigins: [...allowedOriginHostnames] } : {}),\n });\n\n const trustProxy = resolveTrustProxySetting(process.env['FS_TRUST_PROXY']);\n if (trustProxy !== undefined) {\n app.set('trust proxy', trustProxy);\n }\n\n if (allowedHosts.length === 0) {\n Logger.warn(\n '[HTTP] FS_ALLOW_UNRESTRICTED_HOSTS is set: binding globally without Host validation.',\n );\n }\n\n app.options('/mcp', corsPreflightHandler(allowedOriginHostnames));\n app.use('/mcp', corsOriginMiddleware(allowedOriginHostnames));\n\n // Unconditional: the spec's rate-limit MUST is not scoped to authenticated\n // binds. A keyless bind is loopback-only, so the cap is looser, not absent.\n const rpm = parseEnvInt('FS_RATE_LIMIT_RPM', apiKey ? 120 : 6_000, 1, 100_000);\n app.use('/mcp', createRateLimiter(rpm));\n\n if (apiKey && allowedHosts.length > 0) {\n const metadataHandler = (req: Request, res: Response): void => {\n const resource = protectedResourceUrl(req, true, publicUrl);\n if (!resource) {\n // Unreachable for the unrestricted case (the gate above excludes it),\n // but defend a FS_PUBLIC_URL parse failure with a bodyless\n // 404 — a generic OAuth client cannot parse a JSON-RPC error envelope.\n res.status(404).end();\n return;\n }\n res.header('Access-Control-Allow-Origin', '*');\n res.status(200).json({\n resource: resource.href,\n bearer_methods_supported: ['header'],\n resource_name: 'filesystem-mcp',\n });\n };\n app.get(\n ['/.well-known/oauth-protected-resource', '/.well-known/oauth-protected-resource/mcp'],\n metadataHandler,\n );\n }\n\n app.get('/healthz', (_req: Request, res: Response) => {\n res.status(200).json({\n status: 'ok',\n uptime: process.uptime(),\n });\n });\n\n app.use('/mcp', bearerAuthMiddleware(apiKey, allowedHosts.length > 0, publicUrl));\n\n const resourceUpdateSink = (uri: string): void => {\n notifier.resourceUpdated(uri);\n };\n\n app.post('/mcp', (req: Request, res: Response, next: NextFunction) => {\n void (async () => {\n if (!isJsonContentType(req.headers['content-type'])) {\n sendJsonRpcError(\n res,\n 415,\n JSONRPC_SERVER_ERROR,\n 'Unsupported Media Type: Content-Type must be application/json',\n );\n return;\n }\n const parsedBody = req.body as unknown;\n if (parsedBody === undefined) {\n // Undefined tells the Node adapter to read the raw stream without our parser's limit.\n sendJsonRpcError(res, 400, ProtocolErrorCode.ParseError, 'Invalid JSON in request body');\n return;\n }\n // Only a structurally valid listen gets watchers, mirroring the stdio\n // gate. A malformed one cannot succeed downstream, so attaching handles\n // for it only gives the response-close release something to undo — and\n // everything that is not a listen takes no watchers either way.\n if (!isStructurallyValidListen(parsedBody)) {\n await modernNodeHandler(req, res, parsedBody);\n return;\n }\n\n // Reject an over-cap listen before the ack so the client does not believe\n // every requested URI is watched when the watcher budget is exhausted.\n // The per-URI `capped` failure below would also reject, but only after\n // creating and tearing down every watcher up to the cap.\n const requestedUris = listenSubscriptionUris(parsedBody);\n // Only URIs without a live watcher consume a slot; a re-listen to an\n // already-watched URI just retains another lease.\n const newUris = requestedUris.filter((uri) => !sharedRegistry.hasWatcher(uri));\n const available = MAX_WATCHERS - sharedRegistry.size();\n if (newUris.length > available) {\n sendJsonRpcError(\n res,\n 400,\n ProtocolErrorCode.InvalidParams,\n `subscriptions/listen names ${newUris.length} not-yet-watched URIs but only ${available} watcher slots remain (cap ${MAX_WATCHERS}). Reduce the resourceSubscriptions list.`,\n jsonRpcRequestId(parsedBody),\n );\n return;\n }\n\n const prepared = await prepareListenWatchers(\n parsedBody,\n sharedPathGuard,\n sharedRegistry,\n resourceUpdateSink,\n );\n if (!prepared.ok) {\n sendJsonRpcError(\n res,\n 400,\n ProtocolErrorCode.InvalidParams,\n prepared.message,\n jsonRpcRequestId(parsedBody),\n );\n return;\n }\n\n let released = false;\n const release = (): void => {\n if (released) return;\n released = true;\n for (const uri of prepared.acquiredUris) sharedRegistry.release(uri);\n };\n if (prepared.acquiredUris.length > 0) {\n // The attach loop awaits per-URI path validation, so the client can be\n // gone by the time it returns. `close` fires once: a listener attached\n // after it would never run and every lease this batch took would leak\n // until process shutdown.\n if (res.closed || res.destroyed || res.writableEnded) release();\n else res.once('close', release);\n }\n\n try {\n await modernNodeHandler(req, res, parsedBody);\n } catch (error) {\n release();\n throw error;\n }\n })().catch(next);\n });\n\n // Answer non-POST here rather than handing it to `modernNodeHandler`. The\n // SDK's own 405 is byte-identical to this one, but it omits the `Allow`\n // header RFC 9110 §15.5.6 requires and routes every routine GET probe through\n // the handler's `onerror` — which this server logs at error level.\n app.all('/mcp', (_req: Request, res: Response) => {\n sendJsonRpcError(res, 405, JSONRPC_SERVER_ERROR, 'Method not allowed.', null, {\n Allow: 'POST, OPTIONS',\n });\n });\n\n app.use(errorHandlerMiddleware);\n\n return app;\n}\n\nexport async function startHttpServer(\n port: number,\n options: ServerOptions,\n config: RuntimeConfig = {},\n): Promise<Server> {\n const httpHost = config.httpHost ?? '127.0.0.1';\n const { apiKey } = config;\n assertHttpBindingPolicy(httpHost, apiKey);\n const allowedHosts = resolveAllowedHosts(httpHost, process.env['FS_ALLOWED_HOSTS']);\n assertHttpHostPolicy(\n httpHost,\n allowedHosts,\n parseTrueEnvFlag(process.env['FS_ALLOW_UNRESTRICTED_HOSTS'], 'FS_ALLOW_UNRESTRICTED_HOSTS'),\n );\n\n const sharedRegistry = createWatcherRegistry();\n // One store for the whole endpoint, same lifetime and same one-credential\n // trust argument as the shared guard below: a result a tool externalized in\n // one POST must survive to the follow-up resources/read, and the modern leg\n // builds a fresh McpServer per request so a per-request store would discard\n // it immediately.\n // Fires only from a tool call, which is long after `modernHandler` below is\n // constructed, so reading it from the closure is safe (same as `getNotifier`).\n const sharedStore = new ResourceStore(() => {\n modernHandler.notify.resourcesChanged();\n });\n const sharedPageStore = new PageSnapshotStore();\n // One guard for the whole endpoint. The modern leg builds a fresh McpServer\n // per request, so a per-instance guard would discard every accepted access\n // grant the moment the request ended — re-prompting on each subsequent call\n // and leaving the listen-watcher path validating against a stale allowed set.\n // Grant scope is therefore the endpoint, not the connection: with API_KEY set\n // every caller presents the same key (one auth context by construction), and\n // without it the bind is loopback-only. Split into per-auth-context guards if\n // this ever serves more than one credential.\n const sharedPathGuard = new PathGuard(options);\n await sharedPathGuard.recomputeAllowedDirectories();\n\n const modernHandler: McpHttpHandler = createMcpHandler(\n makeHttpModernFactory(\n options,\n () => modernHandler.notify,\n sharedRegistry,\n sharedPathGuard,\n sharedStore,\n sharedPageStore,\n apiKey,\n ),\n {\n legacy: 'reject',\n onerror: (error: Error) => {\n Logger.error('[HTTP] modern leg error:', formatUnknownErrorMessage(error));\n },\n },\n );\n const modernNodeHandler = toNodeHandler(modernHandler);\n\n const app = setupExpressApp(\n httpHost,\n apiKey,\n allowedHosts,\n modernNodeHandler,\n sharedPathGuard,\n sharedRegistry,\n modernHandler.notify,\n );\n\n const httpServer = createHttpServer(app);\n // Must exceed the idle timeout of any proxy in front of this server, or the\n // proxy reuses connections the server already closed (intermittent 502s).\n const keepAliveMs = parseEnvInt('FS_KEEPALIVE_TIMEOUT_MS', 5_000, 1_000, 600_000);\n httpServer.keepAliveTimeout = keepAliveMs;\n httpServer.headersTimeout = keepAliveMs + 5_000;\n httpServer.requestTimeout = DEFAULT_REQUEST_TIMEOUT_MSEC;\n\n const onHttpServerError = (error: Error): void => {\n Logger.error('[HTTP] runtime server error', {\n host: httpHost,\n port,\n error: formatUnknownErrorMessage(error),\n });\n };\n httpServer.on('error', onHttpServerError);\n\n const originalClose = httpServer.close.bind(httpServer);\n httpServer.close = function (callback?: (error?: Error) => void) {\n sharedRegistry.destroy();\n sharedPageStore.clear();\n modernHandler\n .close()\n .then(() => {\n originalClose(callback);\n })\n .catch((err: unknown) => {\n Logger.error(\n '[HTTP] Error closing handler during HTTP server close:',\n formatUnknownErrorMessage(err),\n );\n originalClose(callback);\n });\n return httpServer;\n };\n\n return new Promise((resolve, reject) => {\n const onError = (err: Error) => {\n sharedRegistry.destroy();\n sharedPageStore.clear();\n modernHandler.close().catch((closeErr: unknown) => {\n Logger.error(\n '[HTTP] Error closing handler on startup failure:',\n formatUnknownErrorMessage(closeErr),\n );\n });\n reject(err);\n };\n httpServer.once('error', onError);\n\n httpServer.listen(port, httpHost, () => {\n httpServer.removeListener('error', onError);\n Logger.info(`[HTTP] Server listening on http://${httpHost}:${port}`);\n resolve(httpServer);\n });\n });\n}\n"]}
|
package/dist/transport/stdio.js
CHANGED
|
@@ -93,7 +93,7 @@ export async function seedRootsFromClient(ctx) {
|
|
|
93
93
|
export function startServer(options, config = {}) {
|
|
94
94
|
let closed = false;
|
|
95
95
|
let activeCtx;
|
|
96
|
-
const pathGuard = new PathGuard(options
|
|
96
|
+
const pathGuard = new PathGuard(options);
|
|
97
97
|
let pathGuardReady;
|
|
98
98
|
const ensurePathGuard = () => {
|
|
99
99
|
pathGuardReady ??= pathGuard.recomputeAllowedDirectories();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/transport/stdio.ts"],"names":[],"mappings":"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","sourcesContent":["// stdio hosting: the pinned-instance factory, legacy roots seeding, and the\n// gated transport that attaches listen-filter watchers before the SDK sees the\n// message.\nimport type { McpServerFactory } from '@modelcontextprotocol/server';\nimport { ProtocolErrorCode } from '@modelcontextprotocol/server';\nimport {\n serveStdio,\n type StdioServerHandle,\n StdioServerTransport,\n} from '@modelcontextprotocol/server/stdio';\n\nimport { fileURLToPath } from 'node:url';\n\nimport { formatUnknownErrorMessage } from '../core/errors.js';\nimport { Logger } from '../core/observability.js';\nimport type { ServerOptions } from '../core/path.js';\nimport { PathGuard } from '../core/path.js';\nimport { createWatcherRegistry } from '../core/watcher-registry.js';\nimport type { FilesystemServerContext } from '../server.js';\nimport { createServer } from '../server.js';\nimport type { RuntimeConfig } from './shared.js';\nimport {\n isStructurallyValidListen,\n jsonRpcError,\n jsonRpcRequestId,\n listenSubscriptionUris,\n prepareListenWatchers,\n} from './shared.js';\n\ninterface StdioListenState {\n acquiredUris: string[];\n cancelled: boolean;\n delivered: boolean;\n}\n\n// Read through a function, not `state.cancelled` directly: a cancellation lands\n// between the checks, and control-flow narrowing from the first one would\n// otherwise make the second read a compile-time constant.\nfunction isListenCancelled(state: StdioListenState): boolean {\n return state.cancelled;\n}\n\nfunction cancelledRequestId(message: unknown): string | number | null {\n if (typeof message !== 'object' || message === null) return null;\n const notification = message as {\n method?: unknown;\n params?: { requestId?: unknown };\n };\n if (notification.method !== 'notifications/cancelled') return null;\n const requestId = notification.params?.requestId;\n return typeof requestId === 'string' || typeof requestId === 'number' ? requestId : null;\n}\n\n/**\n * Seed the guard's allowed roots from the client's declared workspace roots.\n * Legacy-era only: push-style `roots/list` is deprecated (SEP-2577) and throws\n * on a 2026-07-28 connection, where clients pass paths as tool arguments and\n * the access-grant round-trip covers out-of-root paths instead. Every root\n * still passes `applyGrant`'s boundary and unsafe-path guards, so a client\n * cannot root-declare its way into $HOME or past FS_ROOT_BOUNDARY — a refused\n * root is skipped and its paths fail closed at validateAccess like any other.\n */\nexport async function seedRootsFromClient(ctx: FilesystemServerContext): Promise<number> {\n let roots: readonly { uri: string }[];\n try {\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- legacy-era-only; the modern era never reaches the hooks that call this.\n ({ roots } = await ctx.mcp.server.listRoots());\n } catch (err: unknown) {\n // Client without the roots capability (strict-capabilities throw), or the\n // request failed — either way there is nothing to seed.\n Logger.debug('[Stdio] roots/list unavailable', { error: formatUnknownErrorMessage(err) });\n return 0;\n }\n let granted = 0;\n for (const root of roots) {\n let dir: string;\n try {\n dir = fileURLToPath(root.uri);\n } catch {\n continue; // not a file:// root\n }\n try {\n if (await ctx.pathGuard.applyGrant(dir)) granted += 1;\n else Logger.debug('[Stdio] client root refused by grant policy', { dir });\n } catch (err: unknown) {\n Logger.debug('[Stdio] client root grant failed', {\n dir,\n error: formatUnknownErrorMessage(err),\n });\n }\n }\n // No list-changed notification follows a grant. Allowed roots were once listed\n // as concrete resources, and are not any more: the file template's `list()`\n // returns nothing and the other two lists never read the roots at all (see\n // resources.ts). Notifying bought the client a re-fetch of an unchanged list.\n if (granted > 0) {\n Logger.info(`[Stdio] allowed ${granted} client-declared workspace root(s)`);\n }\n return granted;\n}\n\n/**\n * Serve filesystem-mcp over stdio using modern protocol revision 2026-07-28.\n *\n * The SDK's `StdioListenRouter` acknowledges `subscriptions/listen` and routes\n * the pinned instance's outbound change notifications onto the matching\n * streams, but it exposes no listen-filter hook, so nothing attaches the\n * filesystem watcher that would produce those notifications. `serveStdio`'s\n * `transport` option is the seam: it installs its own `onmessage` synchronously\n * and only then starts the wire, so wrapping that callback afterwards gives the\n * same view of the inbound `resourceSubscriptions` filter the HTTP leg reads off\n * `req.body`. The watcher's notify sink calls `sendResourceUpdated` on the\n * pinned instance, which the router then delivers to the listening stream.\n *\n * Watcher leases are tracked by listen request ID and released on cancellation,\n * rejection, graceful completion, or connection close.\n */\nexport function startServer(options: ServerOptions, config: RuntimeConfig = {}): StdioServerHandle {\n let closed = false;\n let activeCtx: FilesystemServerContext | undefined;\n const pathGuard = new PathGuard(options, true);\n let pathGuardReady: Promise<void> | undefined;\n const ensurePathGuard = (): Promise<void> => {\n pathGuardReady ??= pathGuard.recomputeAllowedDirectories();\n return pathGuardReady;\n };\n // Shared with the resource contract so a `resources/subscribe` and a\n // `subscriptions/listen` naming the same URI reuse one watcher.\n const registry = createWatcherRegistry();\n const factory: McpServerFactory = async ({ era }) => {\n await ensurePathGuard();\n const c = await createServer(options, {\n watcherRegistry: registry,\n pathGuard,\n era,\n ...(config.apiKey !== undefined ? { apiKey: config.apiKey } : {}),\n });\n if (closed) {\n // The connection went away while this instance was being built.\n // `cleanupConnection` already ran and its `closed` guard makes every later\n // call a no-op, so publishing `c` would strand it: nothing would dispose\n // it. Return it unactivated — the wire is gone, so nothing serves it.\n c.disposeRuntimeState();\n return c.mcp;\n }\n activeCtx = c;\n if (era === 'legacy') {\n // Fires when the client's `notifications/initialized` lands. Safe to own:\n // the SDK's only touchpoint is its own initialized handler reading it.\n c.mcp.server.oninitialized = () => {\n void seedRootsFromClient(c);\n };\n // Re-list and grant anything new. Grants are session-additive (R8): a\n // root the client withdrew is not revoked mid-session.\n c.mcp.server.setNotificationHandler('notifications/roots/list_changed', () => {\n void seedRootsFromClient(c);\n });\n }\n return c.mcp;\n };\n\n // One sink for the whole connection. `addCallback` de-duplicates by function\n // identity, so re-using this closure makes a repeat `subscriptions/listen` on\n // an already-watched URI a no-op; a fresh closure per listen would instead\n // stack a callback every time, double-notifying and growing an\n // `activeCallbacks` set that MAX_WATCHERS does not bound (it caps watchers,\n // not callbacks). Reads `activeCtx` when it fires rather than capturing it, so\n // it never pins a disposed instance.\n const sink = (uri: string): void => {\n // A failed notify means the connection went away; nothing to recover.\n void activeCtx?.mcp.server.sendResourceUpdated({ uri }).catch((err: unknown) => {\n Logger.debug('[Stdio] resource update not delivered', {\n uri,\n error: formatUnknownErrorMessage(err),\n });\n });\n };\n\n const wire = new StdioServerTransport();\n const listens = new Map<string | number, StdioListenState>();\n\n // Deleting the entry is what makes this idempotent: a second call for the\n // same id finds nothing to release.\n const releaseListen = (id: string | number): void => {\n const state = listens.get(id);\n if (!state) return;\n listens.delete(id);\n for (const uri of state.acquiredUris) registry.release(uri);\n };\n\n const cleanupConnection = (): void => {\n if (closed) return;\n closed = true;\n for (const state of listens.values()) state.cancelled = true;\n listens.clear();\n registry.destroy();\n const ctx = activeCtx;\n activeCtx = undefined;\n // Both steps below are guarded separately, and for the same reason: this\n // also runs from `wire.onclose`, where a throw is an uncaught exception on\n // the stdin close event rather than a rejected close(). One shared catch\n // would let a failed disposal skip the unref and strand the process.\n try {\n ctx?.disposeRuntimeState();\n } catch {\n /* idempotent — disposeRuntimeState guards cleanedUp */\n }\n try {\n // The SDK's transport close pauses stdin only when no other 'data'\n // listener is left, so a fatal read error (a ReadBuffer overflow) tears\n // the connection down but leaves the handle referenced and this process\n // alive with nothing left to serve. Unref rather than pause: a listener\n // the SDK still owns keeps receiving data, the loop just stops being held\n // open for it.\n process.stdin.unref();\n } catch {\n /* a file-backed stdin is an `fs.ReadStream`, which has no `unref` */\n }\n };\n\n const send = wire.send.bind(wire);\n wire.send = async (message) => {\n if ('id' in message && ('result' in message || 'error' in message)) {\n const id = message.id;\n if (id !== undefined) releaseListen(id);\n }\n await send(message);\n };\n\n const handle = serveStdio(factory, {\n legacy: 'serve',\n transport: wire,\n onerror: (error: unknown) => {\n Logger.error('[Stdio] serve error:', formatUnknownErrorMessage(error));\n },\n });\n const onclose = wire.onclose;\n wire.onclose = () => {\n try {\n cleanupConnection();\n } finally {\n onclose?.();\n }\n };\n\n // Wrap the callback `serveStdio` just installed. Listen requests are admitted\n // in order so two requests naming the same URI cannot race the registry's\n // ref-count. Cancellation is serialized with pending admission by request ID:\n // a pending request is suppressed and releases after preparation, while an\n // active request releases before the SDK closes its stream.\n //\n // serveStdio installs its onmessage synchronously and only then starts the\n // wire; this wrapper is only correct because of that ordering. Assert it\n // rather than degrade to a silent no-op if a future SDK release changes it.\n const deliver = wire.onmessage;\n if (!deliver) {\n throw new Error(\n 'serveStdio did not install a synchronous onmessage handler; the subscriptions/listen watcher gate cannot attach. This is an SDK contract change, not a configuration error.',\n );\n }\n let gated = Promise.resolve();\n wire.onmessage = (message) => {\n const cancelId = cancelledRequestId(message);\n if (cancelId !== null) {\n const state = listens.get(cancelId);\n if (!state) {\n deliver(message);\n return;\n }\n state.cancelled = true;\n if (state.delivered) {\n releaseListen(cancelId);\n deliver(message);\n }\n return;\n }\n\n const subscriptionUris = listenSubscriptionUris(message);\n if (subscriptionUris.length === 0) {\n deliver(message);\n return;\n }\n const id = jsonRpcRequestId(message);\n if (id === null || listens.has(id)) {\n deliver(message);\n return;\n }\n const state: StdioListenState = {\n acquiredUris: [],\n cancelled: false,\n delivered: false,\n };\n listens.set(id, state);\n gated = gated\n .then(async () => {\n if (!isStructurallyValidListen(message)) {\n listens.delete(id);\n deliver(message);\n return;\n }\n if (isListenCancelled(state)) {\n listens.delete(id);\n return;\n }\n await ensurePathGuard();\n const prepared = await prepareListenWatchers(message, pathGuard, registry, sink);\n if (!prepared.ok) {\n listens.delete(id);\n await wire.send(jsonRpcError(ProtocolErrorCode.InvalidParams, prepared.message, id));\n return;\n }\n state.acquiredUris = prepared.acquiredUris;\n if (isListenCancelled(state)) {\n releaseListen(id);\n return;\n }\n state.delivered = true;\n deliver(message);\n })\n .catch(async (error: unknown) => {\n releaseListen(id);\n const failure =\n error instanceof Error ? error : new Error(formatUnknownErrorMessage(error));\n wire.onerror?.(failure);\n // The gate swallowed the message, so nothing downstream will answer it.\n // Without this the client waits out its whole request timeout.\n await wire\n .send(jsonRpcError(ProtocolErrorCode.InternalError, failure.message, id))\n .catch(() => {\n /* the wire is gone; onerror above already reported it */\n });\n });\n };\n\n return {\n close: async () => {\n try {\n cleanupConnection();\n } finally {\n await handle.close();\n }\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/transport/stdio.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EACL,UAAU,EAEV,oBAAoB,GACrB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAElD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAEpE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EACL,yBAAyB,EACzB,YAAY,EACZ,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AAQrB,gFAAgF;AAChF,0EAA0E;AAC1E,0DAA0D;AAC1D,SAAS,iBAAiB,CAAC,KAAuB;IAChD,OAAO,KAAK,CAAC,SAAS,CAAC;AACzB,CAAC;AAED,SAAS,kBAAkB,CAAC,OAAgB;IAC1C,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjE,MAAM,YAAY,GAAG,OAGpB,CAAC;IACF,IAAI,YAAY,CAAC,MAAM,KAAK,yBAAyB;QAAE,OAAO,IAAI,CAAC;IACnE,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,EAAE,SAAS,CAAC;IACjD,OAAO,OAAO,SAAS,KAAK,QAAQ,IAAI,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3F,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,GAA4B;IACpE,IAAI,KAAiC,CAAC;IACtC,IAAI,CAAC;QACH,uIAAuI;QACvI,CAAC,EAAE,KAAK,EAAE,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;IACjD,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,0EAA0E;QAC1E,wDAAwD;QACxD,MAAM,CAAC,KAAK,CAAC,gCAAgC,EAAE,EAAE,KAAK,EAAE,yBAAyB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC1F,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,SAAS,CAAC,qBAAqB;QACjC,CAAC;QACD,IAAI,CAAC;YACH,IAAI,MAAM,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC,CAAC;;gBACjD,MAAM,CAAC,KAAK,CAAC,6CAA6C,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QAC5E,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,CAAC,KAAK,CAAC,kCAAkC,EAAE;gBAC/C,GAAG;gBACH,KAAK,EAAE,yBAAyB,CAAC,GAAG,CAAC;aACtC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,+EAA+E;IAC/E,4EAA4E;IAC5E,2EAA2E;IAC3E,8EAA8E;IAC9E,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,MAAM,CAAC,IAAI,CAAC,mBAAmB,OAAO,oCAAoC,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,WAAW,CAAC,OAAsB,EAAE,SAAwB,EAAE;IAC5E,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,SAA8C,CAAC;IACnD,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,cAAyC,CAAC;IAC9C,MAAM,eAAe,GAAG,GAAkB,EAAE;QAC1C,cAAc,KAAK,SAAS,CAAC,2BAA2B,EAAE,CAAC;QAC3D,OAAO,cAAc,CAAC;IACxB,CAAC,CAAC;IACF,qEAAqE;IACrE,gEAAgE;IAChE,MAAM,QAAQ,GAAG,qBAAqB,EAAE,CAAC;IACzC,MAAM,OAAO,GAAqB,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;QAClD,MAAM,eAAe,EAAE,CAAC;QACxB,MAAM,CAAC,GAAG,MAAM,YAAY,CAAC,OAAO,EAAE;YACpC,eAAe,EAAE,QAAQ;YACzB,SAAS;YACT,GAAG;YACH,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClE,CAAC,CAAC;QACH,IAAI,MAAM,EAAE,CAAC;YACX,gEAAgE;YAChE,2EAA2E;YAC3E,yEAAyE;YACzE,sEAAsE;YACtE,CAAC,CAAC,mBAAmB,EAAE,CAAC;YACxB,OAAO,CAAC,CAAC,GAAG,CAAC;QACf,CAAC;QACD,SAAS,GAAG,CAAC,CAAC;QACd,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;YACrB,0EAA0E;YAC1E,uEAAuE;YACvE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,GAAG,GAAG,EAAE;gBAChC,KAAK,mBAAmB,CAAC,CAAC,CAAC,CAAC;YAC9B,CAAC,CAAC;YACF,sEAAsE;YACtE,uDAAuD;YACvD,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,sBAAsB,CAAC,kCAAkC,EAAE,GAAG,EAAE;gBAC3E,KAAK,mBAAmB,CAAC,CAAC,CAAC,CAAC;YAC9B,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,CAAC,CAAC,GAAG,CAAC;IACf,CAAC,CAAC;IAEF,6EAA6E;IAC7E,8EAA8E;IAC9E,2EAA2E;IAC3E,+DAA+D;IAC/D,4EAA4E;IAC5E,+EAA+E;IAC/E,qCAAqC;IACrC,MAAM,IAAI,GAAG,CAAC,GAAW,EAAQ,EAAE;QACjC,sEAAsE;QACtE,KAAK,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;YAC7E,MAAM,CAAC,KAAK,CAAC,uCAAuC,EAAE;gBACpD,GAAG;gBACH,KAAK,EAAE,yBAAyB,CAAC,GAAG,CAAC;aACtC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,IAAI,oBAAoB,EAAE,CAAC;IACxC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAqC,CAAC;IAE7D,0EAA0E;IAC1E,oCAAoC;IACpC,MAAM,aAAa,GAAG,CAAC,EAAmB,EAAQ,EAAE;QAClD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACnB,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,YAAY;YAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC9D,CAAC,CAAC;IAEF,MAAM,iBAAiB,GAAG,GAAS,EAAE;QACnC,IAAI,MAAM;YAAE,OAAO;QACnB,MAAM,GAAG,IAAI,CAAC;QACd,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE;YAAE,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC;QAC7D,OAAO,CAAC,KAAK,EAAE,CAAC;QAChB,QAAQ,CAAC,OAAO,EAAE,CAAC;QACnB,MAAM,GAAG,GAAG,SAAS,CAAC;QACtB,SAAS,GAAG,SAAS,CAAC;QACtB,yEAAyE;QACzE,2EAA2E;QAC3E,yEAAyE;QACzE,qEAAqE;QACrE,IAAI,CAAC;YACH,GAAG,EAAE,mBAAmB,EAAE,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,uDAAuD;QACzD,CAAC;QACD,IAAI,CAAC;YACH,mEAAmE;YACnE,wEAAwE;YACxE,wEAAwE;YACxE,wEAAwE;YACxE,0EAA0E;YAC1E,eAAe;YACf,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,qEAAqE;QACvE,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,IAAI,GAAG,KAAK,EAAE,OAAO,EAAE,EAAE;QAC5B,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,QAAQ,IAAI,OAAO,IAAI,OAAO,IAAI,OAAO,CAAC,EAAE,CAAC;YACnE,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC;YACtB,IAAI,EAAE,KAAK,SAAS;gBAAE,aAAa,CAAC,EAAE,CAAC,CAAC;QAC1C,CAAC;QACD,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC;IACtB,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,EAAE;QACjC,MAAM,EAAE,OAAO;QACf,SAAS,EAAE,IAAI;QACf,OAAO,EAAE,CAAC,KAAc,EAAE,EAAE;YAC1B,MAAM,CAAC,KAAK,CAAC,sBAAsB,EAAE,yBAAyB,CAAC,KAAK,CAAC,CAAC,CAAC;QACzE,CAAC;KACF,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAC7B,IAAI,CAAC,OAAO,GAAG,GAAG,EAAE;QAClB,IAAI,CAAC;YACH,iBAAiB,EAAE,CAAC;QACtB,CAAC;gBAAS,CAAC;YACT,OAAO,EAAE,EAAE,CAAC;QACd,CAAC;IACH,CAAC,CAAC;IAEF,8EAA8E;IAC9E,0EAA0E;IAC1E,8EAA8E;IAC9E,2EAA2E;IAC3E,4DAA4D;IAC5D,EAAE;IACF,2EAA2E;IAC3E,yEAAyE;IACzE,4EAA4E;IAC5E,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC;IAC/B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,6KAA6K,CAC9K,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAC9B,IAAI,CAAC,SAAS,GAAG,CAAC,OAAO,EAAE,EAAE;QAC3B,MAAM,QAAQ,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAC7C,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACpC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,OAAO,CAAC,CAAC;gBACjB,OAAO;YACT,CAAC;YACD,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC;YACvB,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;gBACpB,aAAa,CAAC,QAAQ,CAAC,CAAC;gBACxB,OAAO,CAAC,OAAO,CAAC,CAAC;YACnB,CAAC;YACD,OAAO;QACT,CAAC;QAED,MAAM,gBAAgB,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAC;QACzD,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,OAAO,CAAC,CAAC;YACjB,OAAO;QACT,CAAC;QACD,MAAM,EAAE,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,EAAE,KAAK,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YACnC,OAAO,CAAC,OAAO,CAAC,CAAC;YACjB,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAAqB;YAC9B,YAAY,EAAE,EAAE;YAChB,SAAS,EAAE,KAAK;YAChB,SAAS,EAAE,KAAK;SACjB,CAAC;QACF,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QACvB,KAAK,GAAG,KAAK;aACV,IAAI,CAAC,KAAK,IAAI,EAAE;YACf,IAAI,CAAC,yBAAyB,CAAC,OAAO,CAAC,EAAE,CAAC;gBACxC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACnB,OAAO,CAAC,OAAO,CAAC,CAAC;gBACjB,OAAO;YACT,CAAC;YACD,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC7B,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACnB,OAAO;YACT,CAAC;YACD,MAAM,eAAe,EAAE,CAAC;YACxB,MAAM,QAAQ,GAAG,MAAM,qBAAqB,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;YACjF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACnB,MAAM,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC,aAAa,EAAE,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;gBACrF,OAAO;YACT,CAAC;YACD,KAAK,CAAC,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAC;YAC3C,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC7B,aAAa,CAAC,EAAE,CAAC,CAAC;gBAClB,OAAO;YACT,CAAC;YACD,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC;YACvB,OAAO,CAAC,OAAO,CAAC,CAAC;QACnB,CAAC,CAAC;aACD,KAAK,CAAC,KAAK,EAAE,KAAc,EAAE,EAAE;YAC9B,aAAa,CAAC,EAAE,CAAC,CAAC;YAClB,MAAM,OAAO,GACX,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,yBAAyB,CAAC,KAAK,CAAC,CAAC,CAAC;YAC/E,IAAI,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;YACxB,wEAAwE;YACxE,+DAA+D;YAC/D,MAAM,IAAI;iBACP,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC,aAAa,EAAE,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;iBACxE,KAAK,CAAC,GAAG,EAAE;gBACV,yDAAyD;YAC3D,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;IACP,CAAC,CAAC;IAEF,OAAO;QACL,KAAK,EAAE,KAAK,IAAI,EAAE;YAChB,IAAI,CAAC;gBACH,iBAAiB,EAAE,CAAC;YACtB,CAAC;oBAAS,CAAC;gBACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;YACvB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["// stdio hosting: the pinned-instance factory, legacy roots seeding, and the\n// gated transport that attaches listen-filter watchers before the SDK sees the\n// message.\nimport type { McpServerFactory } from '@modelcontextprotocol/server';\nimport { ProtocolErrorCode } from '@modelcontextprotocol/server';\nimport {\n serveStdio,\n type StdioServerHandle,\n StdioServerTransport,\n} from '@modelcontextprotocol/server/stdio';\n\nimport { fileURLToPath } from 'node:url';\n\nimport { formatUnknownErrorMessage } from '../core/errors.js';\nimport { Logger } from '../core/observability.js';\nimport type { ServerOptions } from '../core/path.js';\nimport { PathGuard } from '../core/path.js';\nimport { createWatcherRegistry } from '../core/watcher-registry.js';\nimport type { FilesystemServerContext } from '../server.js';\nimport { createServer } from '../server.js';\nimport type { RuntimeConfig } from './shared.js';\nimport {\n isStructurallyValidListen,\n jsonRpcError,\n jsonRpcRequestId,\n listenSubscriptionUris,\n prepareListenWatchers,\n} from './shared.js';\n\ninterface StdioListenState {\n acquiredUris: string[];\n cancelled: boolean;\n delivered: boolean;\n}\n\n// Read through a function, not `state.cancelled` directly: a cancellation lands\n// between the checks, and control-flow narrowing from the first one would\n// otherwise make the second read a compile-time constant.\nfunction isListenCancelled(state: StdioListenState): boolean {\n return state.cancelled;\n}\n\nfunction cancelledRequestId(message: unknown): string | number | null {\n if (typeof message !== 'object' || message === null) return null;\n const notification = message as {\n method?: unknown;\n params?: { requestId?: unknown };\n };\n if (notification.method !== 'notifications/cancelled') return null;\n const requestId = notification.params?.requestId;\n return typeof requestId === 'string' || typeof requestId === 'number' ? requestId : null;\n}\n\n/**\n * Seed the guard's allowed roots from the client's declared workspace roots.\n * Legacy-era only: push-style `roots/list` is deprecated (SEP-2577) and throws\n * on a 2026-07-28 connection, where clients pass paths as tool arguments and\n * the access-grant round-trip covers out-of-root paths instead. Every root\n * still passes `applyGrant`'s boundary and unsafe-path guards, so a client\n * cannot root-declare its way into $HOME or past FS_ROOT_BOUNDARY — a refused\n * root is skipped and its paths fail closed at validateAccess like any other.\n */\nexport async function seedRootsFromClient(ctx: FilesystemServerContext): Promise<number> {\n let roots: readonly { uri: string }[];\n try {\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- legacy-era-only; the modern era never reaches the hooks that call this.\n ({ roots } = await ctx.mcp.server.listRoots());\n } catch (err: unknown) {\n // Client without the roots capability (strict-capabilities throw), or the\n // request failed — either way there is nothing to seed.\n Logger.debug('[Stdio] roots/list unavailable', { error: formatUnknownErrorMessage(err) });\n return 0;\n }\n let granted = 0;\n for (const root of roots) {\n let dir: string;\n try {\n dir = fileURLToPath(root.uri);\n } catch {\n continue; // not a file:// root\n }\n try {\n if (await ctx.pathGuard.applyGrant(dir)) granted += 1;\n else Logger.debug('[Stdio] client root refused by grant policy', { dir });\n } catch (err: unknown) {\n Logger.debug('[Stdio] client root grant failed', {\n dir,\n error: formatUnknownErrorMessage(err),\n });\n }\n }\n // No list-changed notification follows a grant. Allowed roots were once listed\n // as concrete resources, and are not any more: the file template's `list()`\n // returns nothing and the other two lists never read the roots at all (see\n // resources.ts). Notifying bought the client a re-fetch of an unchanged list.\n if (granted > 0) {\n Logger.info(`[Stdio] allowed ${granted} client-declared workspace root(s)`);\n }\n return granted;\n}\n\n/**\n * Serve filesystem-mcp over stdio using modern protocol revision 2026-07-28.\n *\n * The SDK's `StdioListenRouter` acknowledges `subscriptions/listen` and routes\n * the pinned instance's outbound change notifications onto the matching\n * streams, but it exposes no listen-filter hook, so nothing attaches the\n * filesystem watcher that would produce those notifications. `serveStdio`'s\n * `transport` option is the seam: it installs its own `onmessage` synchronously\n * and only then starts the wire, so wrapping that callback afterwards gives the\n * same view of the inbound `resourceSubscriptions` filter the HTTP leg reads off\n * `req.body`. The watcher's notify sink calls `sendResourceUpdated` on the\n * pinned instance, which the router then delivers to the listening stream.\n *\n * Watcher leases are tracked by listen request ID and released on cancellation,\n * rejection, graceful completion, or connection close.\n */\nexport function startServer(options: ServerOptions, config: RuntimeConfig = {}): StdioServerHandle {\n let closed = false;\n let activeCtx: FilesystemServerContext | undefined;\n const pathGuard = new PathGuard(options);\n let pathGuardReady: Promise<void> | undefined;\n const ensurePathGuard = (): Promise<void> => {\n pathGuardReady ??= pathGuard.recomputeAllowedDirectories();\n return pathGuardReady;\n };\n // Shared with the resource contract so a `resources/subscribe` and a\n // `subscriptions/listen` naming the same URI reuse one watcher.\n const registry = createWatcherRegistry();\n const factory: McpServerFactory = async ({ era }) => {\n await ensurePathGuard();\n const c = await createServer(options, {\n watcherRegistry: registry,\n pathGuard,\n era,\n ...(config.apiKey !== undefined ? { apiKey: config.apiKey } : {}),\n });\n if (closed) {\n // The connection went away while this instance was being built.\n // `cleanupConnection` already ran and its `closed` guard makes every later\n // call a no-op, so publishing `c` would strand it: nothing would dispose\n // it. Return it unactivated — the wire is gone, so nothing serves it.\n c.disposeRuntimeState();\n return c.mcp;\n }\n activeCtx = c;\n if (era === 'legacy') {\n // Fires when the client's `notifications/initialized` lands. Safe to own:\n // the SDK's only touchpoint is its own initialized handler reading it.\n c.mcp.server.oninitialized = () => {\n void seedRootsFromClient(c);\n };\n // Re-list and grant anything new. Grants are session-additive (R8): a\n // root the client withdrew is not revoked mid-session.\n c.mcp.server.setNotificationHandler('notifications/roots/list_changed', () => {\n void seedRootsFromClient(c);\n });\n }\n return c.mcp;\n };\n\n // One sink for the whole connection. `addCallback` de-duplicates by function\n // identity, so re-using this closure makes a repeat `subscriptions/listen` on\n // an already-watched URI a no-op; a fresh closure per listen would instead\n // stack a callback every time, double-notifying and growing an\n // `activeCallbacks` set that MAX_WATCHERS does not bound (it caps watchers,\n // not callbacks). Reads `activeCtx` when it fires rather than capturing it, so\n // it never pins a disposed instance.\n const sink = (uri: string): void => {\n // A failed notify means the connection went away; nothing to recover.\n void activeCtx?.mcp.server.sendResourceUpdated({ uri }).catch((err: unknown) => {\n Logger.debug('[Stdio] resource update not delivered', {\n uri,\n error: formatUnknownErrorMessage(err),\n });\n });\n };\n\n const wire = new StdioServerTransport();\n const listens = new Map<string | number, StdioListenState>();\n\n // Deleting the entry is what makes this idempotent: a second call for the\n // same id finds nothing to release.\n const releaseListen = (id: string | number): void => {\n const state = listens.get(id);\n if (!state) return;\n listens.delete(id);\n for (const uri of state.acquiredUris) registry.release(uri);\n };\n\n const cleanupConnection = (): void => {\n if (closed) return;\n closed = true;\n for (const state of listens.values()) state.cancelled = true;\n listens.clear();\n registry.destroy();\n const ctx = activeCtx;\n activeCtx = undefined;\n // Both steps below are guarded separately, and for the same reason: this\n // also runs from `wire.onclose`, where a throw is an uncaught exception on\n // the stdin close event rather than a rejected close(). One shared catch\n // would let a failed disposal skip the unref and strand the process.\n try {\n ctx?.disposeRuntimeState();\n } catch {\n /* idempotent — disposeRuntimeState guards cleanedUp */\n }\n try {\n // The SDK's transport close pauses stdin only when no other 'data'\n // listener is left, so a fatal read error (a ReadBuffer overflow) tears\n // the connection down but leaves the handle referenced and this process\n // alive with nothing left to serve. Unref rather than pause: a listener\n // the SDK still owns keeps receiving data, the loop just stops being held\n // open for it.\n process.stdin.unref();\n } catch {\n /* a file-backed stdin is an `fs.ReadStream`, which has no `unref` */\n }\n };\n\n const send = wire.send.bind(wire);\n wire.send = async (message) => {\n if ('id' in message && ('result' in message || 'error' in message)) {\n const id = message.id;\n if (id !== undefined) releaseListen(id);\n }\n await send(message);\n };\n\n const handle = serveStdio(factory, {\n legacy: 'serve',\n transport: wire,\n onerror: (error: unknown) => {\n Logger.error('[Stdio] serve error:', formatUnknownErrorMessage(error));\n },\n });\n const onclose = wire.onclose;\n wire.onclose = () => {\n try {\n cleanupConnection();\n } finally {\n onclose?.();\n }\n };\n\n // Wrap the callback `serveStdio` just installed. Listen requests are admitted\n // in order so two requests naming the same URI cannot race the registry's\n // ref-count. Cancellation is serialized with pending admission by request ID:\n // a pending request is suppressed and releases after preparation, while an\n // active request releases before the SDK closes its stream.\n //\n // serveStdio installs its onmessage synchronously and only then starts the\n // wire; this wrapper is only correct because of that ordering. Assert it\n // rather than degrade to a silent no-op if a future SDK release changes it.\n const deliver = wire.onmessage;\n if (!deliver) {\n throw new Error(\n 'serveStdio did not install a synchronous onmessage handler; the subscriptions/listen watcher gate cannot attach. This is an SDK contract change, not a configuration error.',\n );\n }\n let gated = Promise.resolve();\n wire.onmessage = (message) => {\n const cancelId = cancelledRequestId(message);\n if (cancelId !== null) {\n const state = listens.get(cancelId);\n if (!state) {\n deliver(message);\n return;\n }\n state.cancelled = true;\n if (state.delivered) {\n releaseListen(cancelId);\n deliver(message);\n }\n return;\n }\n\n const subscriptionUris = listenSubscriptionUris(message);\n if (subscriptionUris.length === 0) {\n deliver(message);\n return;\n }\n const id = jsonRpcRequestId(message);\n if (id === null || listens.has(id)) {\n deliver(message);\n return;\n }\n const state: StdioListenState = {\n acquiredUris: [],\n cancelled: false,\n delivered: false,\n };\n listens.set(id, state);\n gated = gated\n .then(async () => {\n if (!isStructurallyValidListen(message)) {\n listens.delete(id);\n deliver(message);\n return;\n }\n if (isListenCancelled(state)) {\n listens.delete(id);\n return;\n }\n await ensurePathGuard();\n const prepared = await prepareListenWatchers(message, pathGuard, registry, sink);\n if (!prepared.ok) {\n listens.delete(id);\n await wire.send(jsonRpcError(ProtocolErrorCode.InvalidParams, prepared.message, id));\n return;\n }\n state.acquiredUris = prepared.acquiredUris;\n if (isListenCancelled(state)) {\n releaseListen(id);\n return;\n }\n state.delivered = true;\n deliver(message);\n })\n .catch(async (error: unknown) => {\n releaseListen(id);\n const failure =\n error instanceof Error ? error : new Error(formatUnknownErrorMessage(error));\n wire.onerror?.(failure);\n // The gate swallowed the message, so nothing downstream will answer it.\n // Without this the client waits out its whole request timeout.\n await wire\n .send(jsonRpcError(ProtocolErrorCode.InternalError, failure.message, id))\n .catch(() => {\n /* the wire is gone; onerror above already reported it */\n });\n });\n };\n\n return {\n close: async () => {\n try {\n cleanupConnection();\n } finally {\n await handle.close();\n }\n },\n };\n}\n"]}
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export
|
|
2
|
-
export { listenSubscriptionUris } from './transport/shared.js';
|
|
3
|
-
export { seedRootsFromClient, startServer } from './transport/stdio.js';
|
|
1
|
+
export { startServer } from './transport/stdio.js';
|
|
4
2
|
export { startHttpServer } from './transport/http.js';
|
|
5
3
|
//# sourceMappingURL=transport.d.ts.map
|
package/dist/transport.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/transport.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
// Facade for the transport layer, preserving the package's `./transport`
|
|
2
|
+
// export. The hosting itself lives in `transport/stdio.ts` (pinned-instance
|
|
3
|
+
// stdio serving) and `transport/http.ts` (per-request HTTP hosting);
|
|
4
|
+
// `transport/shared.ts` holds the pieces both legs gate on.
|
|
5
|
+
export { startServer } from './transport/stdio.js';
|
|
3
6
|
export { startHttpServer } from './transport/http.js';
|
|
4
7
|
//# sourceMappingURL=transport.js.map
|
package/dist/transport.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,4EAA4E;AAC5E,qEAAqE;AACrE,4DAA4D;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["// Facade for the transport layer, preserving the package's `./transport`\n// export. The hosting itself lives in `transport/stdio.ts` (pinned-instance\n// stdio serving) and `transport/http.ts` (per-request HTTP hosting);\n// `transport/shared.ts` holds the pieces both legs gate on.\nexport { startServer } from './transport/stdio.js';\nexport { startHttpServer } from './transport/http.js';\n"]}
|
package/package.json
CHANGED