Skip to content

Resource templates ignore enabled: disable() leaves them listed, readable and completable #2693

Description

@sainikhiljuluri

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    v1Issues / PRs related to v1.xv2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions