modelcontextprotocol / modelcontextprotocol/typescript-sdk
Resource templates ignore `enabled`: disable() leaves them listed, readable and completable
Nobody has claimed this yet.
- 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 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: 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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