What happens
disable() on a resource template handle does nothing. The template stays in resources/list and resources/templates/list, is still served by resources/read, and still answers completion/complete.
A static resource registered through the same registerResource call is guarded correctly, so the two behave differently:
const staticRes = server.registerResource('pub', 'res://public', {}, /* ... */);
const tpl = server.registerResource('secret', new ResourceTemplate('secret://{id}', { /* ... */ }), {}, /* ... */);
staticRes.disable();
tpl.disable();
tpl.enabled → false // the flag is set
resources/list → ["secret://alpha"] // still listed
resources/templates/list → ["secret://{id}"] // still listed
read secret://alpha → "SECRET alpha" // still served
read res://public → REJECTED: Resource res://public disabled // static is correct
completion/complete → ["alpha","beta"] // still completing
disable() also fires notifications/resources/list_changed, so the SDK tells clients the list changed while it did not.
Why it happens
enabled is written for resource templates but never read. In packages/server/src/server/mcp.ts on main, tools, prompts and static resources each have guards, and the template paths do not:
resources/list — the template loop only checks listCallback; the static branch three lines above filters on enabled
resources/templates/list — no filter at all
resources/read — the template match has no guard; the static branch eight lines above throws Resource ${uri} disabled
handleResourceCompletion — no guard; the prompt path has if (!prompt.enabled)
This dates to the commit that introduced the feature (PR #247). It added enabled: true plus enable()/disable() to registeredResourceTemplate and added the read sites for tools, prompts and static resources only. git log --all -S "template.enabled" returns no commits on any branch. That PR's own description said RegisteredResourceTemplate would "expose enable() and disable() for quickly toggling whether an item is visible in the corresponding list, or available to be invoked."
Expected
test/e2e/requirements.ts already states the contract for mcpserver:handle:enable-disable:
handle.disable() removes the item from list results and calling/reading it errors; handle.enable() restores it; each transition emits list_changed.
That entry has no knownFailures, and its only scoping note is about stateless hosting. The scenario that verifies it (test/e2e/scenarios/dynamic.test.ts) uses a RegisteredTool, which is why the template gap was never caught.
Impact
RegisteredResourceTemplate publicly exports enabled, enable() and disable(). A server using disable() to withdraw a family of resources is still serving them, and still answering completions for them.
v1.x has the same omission.
Environment
main at 3924de99 (2.0.0-alpha.0); also reproduces on v1.x.
AI assistance was used to investigate and write this report.
What happens
disable()on a resource template handle does nothing. The template stays inresources/listandresources/templates/list, is still served byresources/read, and still answerscompletion/complete.A static resource registered through the same
registerResourcecall is guarded correctly, so the two behave differently:disable()also firesnotifications/resources/list_changed, so the SDK tells clients the list changed while it did not.Why it happens
enabledis written for resource templates but never read. Inpackages/server/src/server/mcp.tsonmain, tools, prompts and static resources each have guards, and the template paths do not:resources/list— the template loop only checkslistCallback; the static branch three lines above filters onenabledresources/templates/list— no filter at allresources/read— the template match has no guard; the static branch eight lines above throwsResource ${uri} disabledhandleResourceCompletion— no guard; the prompt path hasif (!prompt.enabled)This dates to the commit that introduced the feature (PR #247). It added
enabled: trueplusenable()/disable()toregisteredResourceTemplateand added the read sites for tools, prompts and static resources only.git log --all -S "template.enabled"returns no commits on any branch. That PR's own description saidRegisteredResourceTemplatewould "exposeenable()anddisable()for quickly toggling whether an item is visible in the corresponding list, or available to be invoked."Expected
test/e2e/requirements.tsalready states the contract formcpserver:handle:enable-disable:That entry has no
knownFailures, and its only scoping note is about stateless hosting. The scenario that verifies it (test/e2e/scenarios/dynamic.test.ts) uses aRegisteredTool, which is why the template gap was never caught.Impact
RegisteredResourceTemplatepublicly exportsenabled,enable()anddisable(). A server usingdisable()to withdraw a family of resources is still serving them, and still answering completions for them.v1.xhas the same omission.Environment
mainat3924de99(2.0.0-alpha.0); also reproduces onv1.x.AI assistance was used to investigate and write this report.