( req: Request, res: Response, cache: ResolveCache, opts: HttpServerOptions, )
| 191 | } |
| 192 | |
| 193 | async function handleClientRequest( |
| 194 | req: Request, |
| 195 | res: Response, |
| 196 | cache: ResolveCache, |
| 197 | opts: HttpServerOptions, |
| 198 | ): Promise<void> { |
| 199 | const bearer = extractBearer(req); |
| 200 | if (!bearer) { |
| 201 | res.setHeader("WWW-Authenticate", bearerChallenge(req, opts)); |
| 202 | res.status(401).json(jsonRpcError(req.body, -32001, "missing bearer token")); |
| 203 | return; |
| 204 | } |
| 205 | |
| 206 | // Resolve the credential's scope + bound agent. A cache hit skips the |
| 207 | // backend round-trip; a miss probes `whoami` once and caches the result. |
| 208 | // A revoked/expired bearer surfaces as 401 here (InvalidBearerError). |
| 209 | let principal = cache.get(bearer); |
| 210 | if (!principal) { |
| 211 | let resolved: { value: ResolvedPrincipal; cacheable: boolean }; |
| 212 | try { |
| 213 | resolved = await resolvePrincipal(opts, bearer); |
| 214 | } catch (err) { |
| 215 | if (err instanceof InvalidBearerError) { |
| 216 | res.setHeader( |
| 217 | "WWW-Authenticate", |
| 218 | `${bearerChallenge(req, opts)}, error="invalid_token"`, |
| 219 | ); |
| 220 | res.status(401).json(jsonRpcError(req.body, -32001, "invalid bearer token")); |
| 221 | return; |
| 222 | } |
| 223 | throw err; |
| 224 | } |
| 225 | principal = resolved.value; |
| 226 | // Only cache a genuine whoami result. A transient backend failure yields a |
| 227 | // least-privilege fallback that must NOT stick — re-probe next request so |
| 228 | // the session self-corrects once the backend recovers. |
| 229 | if (resolved.cacheable) cache.set(bearer, principal); |
| 230 | } |
| 231 | |
| 232 | // The cold-path whoami probe above is awaited, so the client may have |
| 233 | // disconnected meanwhile. If so, `res`'s "close" has already fired — bail |
| 234 | // before building a transport whose teardown listener would never run. |
| 235 | if (res.closed) return; |
| 236 | |
| 237 | // Stateless: a fresh server + transport per request, torn down when the |
| 238 | // response closes. The SDK forbids reusing a stateless transport across |
| 239 | // requests (message-id collisions), and a fresh transport with |
| 240 | // sessionIdGenerator=undefined skips all session/initialize gating, so a |
| 241 | // bare tools/call dispatches without a prior initialize on this instance. |
| 242 | const client = buildClient(opts, bearer, principal); |
| 243 | const server = buildServer({ client }); |
| 244 | const transport = new StreamableHTTPServerTransport({ |
| 245 | sessionIdGenerator: undefined, |
| 246 | }); |
| 247 | res.on("close", () => { |
| 248 | void transport.close(); |
| 249 | void server.close(); |
| 250 | }); |
no test coverage detected