---
summary: HTTP transport hardening for issue #50 — per-server notifier race fix, bounded-timeout close with close_failures metric, and FinalizationRegistry diagnostic for per-request McpServer/transport retention
breaking: false
---

# 0.6.17 — 2026-04-24

Groundwork for investigating the HTTP heap leak tracked in [#50](https://github.com/cyanheads/mcp-ts-core/issues/50). Fixes a real cross-request correctness bug surfaced during the read-through, tightens the per-request close path so silent hangs can no longer accumulate retained closures, and adds observability to attribute whether instances are actually being reclaimed.

## Fixed

- **`src/mcp-server/tools/tool-registration.ts`** + **`src/mcp-server/resources/resource-registration.ts`** — `registerAll(server)` no longer mutates `this.services.notifyResourceListChanged` / `this.services.notifyResourceUpdated` on a shared registry-wide services object. Under concurrent HTTP requests, request A's `registerAll(serverA)` followed by request B's `registerAll(serverB)` would overwrite the notifier closure, so A's in-flight tool handler could notify `serverB` (or a closed server). Notifiers are now bound per-`registerAll()` call and threaded through to each handler factory.

## Changed

- **`src/mcp-server/tools/utils/toolHandlerFactory.ts`** + **`src/mcp-server/resources/utils/resourceHandlerFactory.ts`** — extracted `HandlerNotifiers` / `ResourceHandlerNotifiers` types for per-server notifier closures. `HandlerFactoryServices` and `ResourceHandlerFactoryServices` no longer carry `notifyResourceListChanged` / `notifyResourceUpdated` fields. `createToolHandler` / `createResourceHandler` take a new third argument (`notifiers`). Internal API change — consumer servers that call `createApp()` are unaffected.
- **`src/mcp-server/transports/http/httpTransport.ts`** — non-SSE per-request cleanup converted from fire-and-forget `transport.close()` / `server.close()` (errors swallowed as `debug` logs) to parallel awaited close under a 5 s bounded timeout via `Promise.all` over a tagged tuple. Failures increment `mcp.http.close_failures{surface,trigger}` and log at `warning`. Error-path transport close uses the same contract.

## Added

- **`mcp.http.close_failures`** counter (tags: `surface` = `transport`|`server`, `trigger` = `success`|`error`). Counts per-request close operations that threw or timed out. Previously invisible; these can now be alerted on.
- **`mcp.http.per_request.created`** and **`mcp.http.per_request.finalized`** counters (tag: `kind` = `server`|`transport`). Every per-request `McpServer` and `McpSessionTransport` is registered with a module-level `FinalizationRegistry`; the `created` counter increments on construction and the `finalized` counter increments when GC actually reclaims the instance. A persistent, traffic-proportional gap between the two is the signature of the retention observed in [#50](https://github.com/cyanheads/mcp-ts-core/issues/50); a gap that closes within minutes is just GC timing. Purely additive observability — no behavior change.

## Dependencies

- **`hono`** — `4.12.14` → `4.12.15` (patch; upstream JWT single-line PEM fix — no impact on this package's auth middleware).
