modelcontextprotocol / modelcontextprotocol/typescript-sdk

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

Open
#2,693 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

v1 v2
Dominant language
TypeScript
Stars
13.4k
Forks
2.2k
Avg merge
3d 15h
Merged PRs (30d)
4

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in packages/server/src/server/mcp.ts, where the resource-template paths for listing, reading, and completion are handled, then review the contract in test/e2e/requirements.ts and the scenario in test/e2e/scenarios/dynamic.test.ts. Done means RegisteredResourceTemplate disable() removes templates from lists and prevents reads and completions, enable() restores them, and both transitions emit list_changed.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.