A cached project router outlives the caller's bearer token. syncUpstreams
only created a McpdUpstream (which binds the token at construction) for
server names not already registered, so after a token rotation every
pre-rotation upstream kept posting the old bearer: six attached, healthy
servers 401'd on tools/list forever — no error anywhere the client could
see — while the two servers attached after the rotation worked.
- discovery.ts: rebuild every upstream on every sync, so it always carries
the current caller token and the current server id (also fixes a stale
serverId after delete+recreate under the same name).
- router.ts: record per-server discovery failures with their messages and
expose them (getToolDiscoveryFailures).
- gate.ts: a partial catalog is now loud — initialize instructions and the
begin_session response name every attached server that contributed no
tools and the error that silenced it.
- tests: regression tests for token rebinding and serverId refresh.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>