You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug] ResourceManager.add_template() ignores warn_on_duplicate_resources and silently overwrites existing templates #3813
In mcp.server.mcpserver.resources.resource_manager.ResourceManager, the add_resource() method properly respects self.warn_on_duplicate_resources (configured via MCPServer(warn_on_duplicate_resources=True), which is True by default): when a duplicate URI is added, it logs a warning (Resource already exists: ...) and preserves the existing resource handler (return existing).
However, add_template() completely ignores warn_on_duplicate_resources:
It does not check self.warn_on_duplicate_resources.
It emits no log warning when an identical URI template is re-registered.
It silently overwrites the existing template in self._templates[template.uri_template] = template.
This creates divergent registration semantics between static resources and template resources registered via @server.resource(...), and leads to silent replacement of template handlers when multiple modules or extensions declare the same URI template.
Example Code
importloggingfrommcp.server.mcpserverimportMCPServerlogging.basicConfig(level=logging.WARNING)
server=MCPServer(warn_on_duplicate_resources=True)
# 1. Static resources: logs warning and preserves existing (doc1)@server.resource("file://doc")defdoc1() ->str:
return"v1"@server.resource("file://doc")defdoc2() ->str:
return"v2"# 2. Template resources: emits no warning and silently overwrites template1 with template2@server.resource("file://doc/{id}")deftemplate1(id: str) ->str:
return"template_v1"@server.resource("file://doc/{id}")deftemplate2(id: str) ->str:
return"template_v2"# Static resource warned and kept doc1:assertserver._resource_manager._resources["file://doc"].fn() =="v1"# Template resource silently overwritten without warning:assertserver._resource_manager._templates["file://doc/{id}"].name=="template2"
Expected output:
When template2 is registered for "file://doc/{id}", ResourceManager should emit: WARNING:mcp.server.mcpserver.resources.resource_manager:Template already exists: file://doc/{id}
and retain or handle the duplicate consistently with add_resource / add_tool / add_prompt.
Confirmed independently and prototyped a fix (not opening a PR — I understand unassigned-contributor PRs get auto-closed here, and this is @Tanmay9223's report to own).
Root cause (src/mcp/server/mcpserver/resources/resource_manager.py): add_template() builds the ResourceTemplate and then does self._templates[template.uri_template] = template unconditionally — there is no existing lookup at all, so warn_on_duplicate_resources is never consulted. add_resource() (same file) and add_tool()/add_prompt() (their managers) all do the lookup, warn, and return existing.
Tested branch, if useful to whoever lands this: fix/3813-duplicate-template-registration on Shanmukh-l33t/python-sdk (compare), commit 02456738 (+37/-0). Three regression tests mirroring the existing test_add_duplicate_resource / test_warn_on_duplicate_resources / test_disable_warn_on_duplicate_resources trio: duplicate registration keeps the first template (second is first), warns with the URI template when enabled, and stays silent but still non-overwriting when warn_on_duplicate_resources=False.
Evidence: the 3 new tests fail on main @ af4c6f4 (3 failed) and pass with the change (test file 14 passed); full suite 6228 passed / 8 skipped / 1 xfailed with one failure, test_file_resources.py::test_permission_error, which fails identically on pristine main in this environment (test runs as root); ruff check + ruff format --check clean; pyright clean on both touched files.
One design note: the duplicate check runs after ResourceTemplate.from_function(), so a re-registration with a malformed function still raises from from_function before the duplicate path — same failure surface as today; happy to move the lookup earlier if you'd prefer duplicates short-circuit before validation.
(AI disclosure: this analysis, patch and tests were drafted with AI assistance and reviewed by me.)
Reproduced by reading src/mcp/server/mcpserver/resources/resource_manager.py on main: add_template writes self._templates[template.uri_template] = template at line 86 with no read of self.warn_on_duplicate_resources, while add_resource at lines 52-58 checks the flag, logs Resource already exists: ... and returns the existing object.
One thing to settle before copying add_resource's behaviour: the return value is discarded. MCPServer.resource() calls self._resource_manager.add_template(...) at server.py:905 as a bare statement and the decorator hands the author their own function back, so "warn and return the existing template" produces a warning plus a registration that silently did nothing, with the author still holding a handler that will never fire. That is better than a silent overwrite, but the two reasonable end states are log-and-keep-first (with the author's second handler dead) or raise on duplicate. For a decorator surface I would raise, because the author is present at import time and a dead handler is exactly the failure mode this issue is about.
Worth noting while you are in there: templates are keyed by the uri_template string, so file://doc/{id} and file://doc/{name} are two keys and both survive, and get_resource will only ever reach one of them for a given URI. String equality dedupe (the cheap fix, matching add_resource) catches the literal duplicate but not the equivalent pattern, so the warn is a partial answer to the divergence the report describes.
@media-ctrl — good catch on the discarded return. I verified it against main, and the dead-handler shape you're worried about already exists verbatim on the sibling path:
server.py:905: self._resource_manager.add_template(...) is a bare statement and the decorator then return fn — confirmed, as you say.
The literal-URI path behaves identically today: MCPServer.add_resource (server.py:786, annotated -> None) discards the manager's return, and @mcp.resource() calls it as a bare statement at server.py:944 before return fn. Registering the same literal URI twice already produces exactly "warning + first registration wins + the author's second function never fires".
So add_template mirroring add_resource (my branch fix/3813-duplicate-template-registration, commit 02456738) makes the two surfaces consistent, which is what #3813 reports: the flag is never consulted for templates.
On raising instead: I agree it's the better end state for a decorator surface — but it's a behavior change spanning both surfaces (otherwise one raises and one warns), which is a design decision for the maintainers, not something to fold into a bug fix for "the flag is ignored". Happy to file that as its own issue if nobody has.
On equivalent patterns: agreed, and no disagreement — file://doc/{id} vs file://doc/{name} are distinct keys and string-equality dedupe can't catch them. Pattern-level equivalence needs real pattern matching plus a tie-break rule for overlapping templates, which is a separate change; I'd keep #3813 to the literal duplicate the report describes.
(AI disclosure: this analysis was drafted with AI assistance and reviewed by me.)
Agreed on scoping #3813 to the literal duplicate. The flag never being consulted is the defect, and add_template mirroring add_resource is the minimal fix that makes the two surfaces tell the same story.
On the raise path, two things that would help whoever opens it:
Cover add_resource and add_template in one issue. If only the template decorator raises, we trade one inconsistency for another.
Name the exception. Authors writing resources today get a warning and a working handler, so a bare ValueError from a decorator will look like a bug in their own code. A dedicated error type is easier to document and to catch.
I can supply the trace from main I posted above plus the shape of a focused test (register twice, assert the exception, assert the first handler still resolves) so the change lands with coverage rather than needing it later. I would rather be a co-reporter than the opener on that one, since the design call belongs to the maintainers.
The minimal patch you sketched is the shape I had already built and tested before this thread picked up: branch fix/3813-duplicate-template-registration at 0245673802afd6cac3c8e938cd26eec89f862399 (compare: main...Shanmukh-l33t:python-sdk:fix/3813-duplicate-template-registration). It adds the existing is not None check mirroring add_resource (resource_manager.py:52-58): warn only when warn_on_duplicate_resources is set, keep the first registration. 3 tests were red at main and green with the fix.
Your point about the discarded return is exactly why I stopped at warn-and-keep-first instead of raising: MCPServer.resource() calls add_template(...) as a bare statement (server.py:905) and hands the author their own function back, so both today's silent overwrite and a warn-only fix leave a dead handler registered — the decorator has no channel to the author at import time except raising.
So on the raise path I'd take your two scoping calls as-is: one issue covering add_resource and add_template together (otherwise the two surfaces just diverge the other way), and a named exception instead of bare ValueError — authors today get a warning and a working handler, so an undifferentiated ValueError out of their own decorator call reads like a bug in their code. The register-twice → assert-exception → assert-first-handler-still-resolves test shape ports straight onto what the warn-path tests already do.
The uri_template equivalence gap you flagged (file://doc/{id} and file://doc/{name} both surviving) is a deeper question — pattern-equivalence dedupe isn't string equality — and I'd keep it out of the literal-duplicate fix entirely rather than block it.
Either way the design call is the maintainers'; the branch is there as the minimal fix or a starting point.
(Written with AI assistance; line references re-verified against main today.)
Raising on duplicate registrations: Agree that moving from warning to raising (e.g. introducing a dedicated DuplicateResourceError) is a cross-cutting design call that should span both add_resource and add_template equally so the surfaces don't diverge.
Pattern-level template equivalence: Differentiating or deduplicating URI template patterns (like {id} vs {name}) requires formal pattern parsing and precedence rules, which definitely belongs in its own feature/SEP discussion.
As the reporter, I'm happy to open the minimal PR for this (and ensure @Shanmukh-l33t is co-authored for the test cases and prototype) if maintainers would like an outside contribution, or happy if the maintainer team prefers to pick it up directly.
(AI disclosure: drafted with AI assistance and reviewed by me.)
Agreed on all three scoping points — your summary matches exactly where the thread landed: literal-duplicate fix now, raise-on-duplicate as one cross-cutting call spanning both adders (named exception included), pattern equivalence in its own SEP.
The reporter opening the minimal PR is the right call — first call on your own issue — so I'll stay out of the way. The minimal fix is on my fork as fix/3813-duplicate-template-registration (commit 0245673802afd6cac3c8e938cd26eec89f862399): a 5-line add_resource mirror in add_template plus 3 warn-flag tests that were red at main and green with it. Cherry-pick it, re-derive it, whatever is easier — and co-author credit is generous of you but not owed; the PR should land under your name either way.
For the raise-path follow-up, the test shape from the branch ports directly: register twice → assert the named exception → assert the first handler still resolves. Happy to review either follow-up when it lands.
Activity
Shanmukh-l33t commentedon Nov 3, 2026
Confirmed independently and prototyped a fix (not opening a PR — I understand unassigned-contributor PRs get auto-closed here, and this is @Tanmay9223's report to own).
Root cause (
src/mcp/server/mcpserver/resources/resource_manager.py):add_template()builds theResourceTemplateand then doesself._templates[template.uri_template] = templateunconditionally — there is noexistinglookup at all, sowarn_on_duplicate_resourcesis never consulted.add_resource()(same file) andadd_tool()/add_prompt()(their managers) all do the lookup, warn, andreturn existing.Fix shape (mirror
add_resource()exactly):Tested branch, if useful to whoever lands this:
fix/3813-duplicate-template-registrationon Shanmukh-l33t/python-sdk (compare), commit02456738(+37/-0). Three regression tests mirroring the existingtest_add_duplicate_resource/test_warn_on_duplicate_resources/test_disable_warn_on_duplicate_resourcestrio: duplicate registration keeps the first template (second is first), warns with the URI template when enabled, and stays silent but still non-overwriting whenwarn_on_duplicate_resources=False.Evidence: the 3 new tests fail on
main@ af4c6f4 (3 failed) and pass with the change (test file 14 passed); full suite 6228 passed / 8 skipped / 1 xfailed with one failure,test_file_resources.py::test_permission_error, which fails identically on pristinemainin this environment (test runs as root);ruff check+ruff format --checkclean;pyrightclean on both touched files.One design note: the duplicate check runs after
ResourceTemplate.from_function(), so a re-registration with a malformed function still raises fromfrom_functionbefore the duplicate path — same failure surface as today; happy to move the lookup earlier if you'd prefer duplicates short-circuit before validation.(AI disclosure: this analysis, patch and tests were drafted with AI assistance and reviewed by me.)
media-ctrl commentedon Nov 4, 2026
Reproduced by reading
src/mcp/server/mcpserver/resources/resource_manager.pyon main:add_templatewritesself._templates[template.uri_template] = templateat line 86 with no read ofself.warn_on_duplicate_resources, whileadd_resourceat lines 52-58 checks the flag, logsResource already exists: ...and returns the existing object.One thing to settle before copying
add_resource's behaviour: the return value is discarded.MCPServer.resource()callsself._resource_manager.add_template(...)atserver.py:905as a bare statement and the decorator hands the author their own function back, so "warn and return the existing template" produces a warning plus a registration that silently did nothing, with the author still holding a handler that will never fire. That is better than a silent overwrite, but the two reasonable end states are log-and-keep-first (with the author's second handler dead) or raise on duplicate. For a decorator surface I would raise, because the author is present at import time and a dead handler is exactly the failure mode this issue is about.Worth noting while you are in there: templates are keyed by the
uri_templatestring, sofile://doc/{id}andfile://doc/{name}are two keys and both survive, andget_resourcewill only ever reach one of them for a given URI. String equality dedupe (the cheap fix, matchingadd_resource) catches the literal duplicate but not the equivalent pattern, so the warn is a partial answer to the divergence the report describes.Minimal patch in the shape of
add_resource:Shanmukh-l33t commentedon Nov 4, 2026
@media-ctrl — good catch on the discarded return. I verified it against main, and the dead-handler shape you're worried about already exists verbatim on the sibling path:
server.py:905:self._resource_manager.add_template(...)is a bare statement and the decorator thenreturn fn— confirmed, as you say.MCPServer.add_resource(server.py:786, annotated-> None) discards the manager's return, and@mcp.resource()calls it as a bare statement atserver.py:944beforereturn fn. Registering the same literal URI twice already produces exactly "warning + first registration wins + the author's second function never fires".So
add_templatemirroringadd_resource(my branchfix/3813-duplicate-template-registration, commit02456738) makes the two surfaces consistent, which is what #3813 reports: the flag is never consulted for templates.On raising instead: I agree it's the better end state for a decorator surface — but it's a behavior change spanning both surfaces (otherwise one raises and one warns), which is a design decision for the maintainers, not something to fold into a bug fix for "the flag is ignored". Happy to file that as its own issue if nobody has.
On equivalent patterns: agreed, and no disagreement —
file://doc/{id}vsfile://doc/{name}are distinct keys and string-equality dedupe can't catch them. Pattern-level equivalence needs real pattern matching plus a tie-break rule for overlapping templates, which is a separate change; I'd keep #3813 to the literal duplicate the report describes.(AI disclosure: this analysis was drafted with AI assistance and reviewed by me.)
media-ctrl commentedon Nov 4, 2026
Agreed on scoping #3813 to the literal duplicate. The flag never being consulted is the defect, and
add_templatemirroringadd_resourceis the minimal fix that makes the two surfaces tell the same story.On the raise path, two things that would help whoever opens it:
add_resourceandadd_templatein one issue. If only the template decorator raises, we trade one inconsistency for another.ValueErrorfrom a decorator will look like a bug in their own code. A dedicated error type is easier to document and to catch.I can supply the trace from
mainI posted above plus the shape of a focused test (register twice, assert the exception, assert the first handler still resolves) so the change lands with coverage rather than needing it later. I would rather be a co-reporter than the opener on that one, since the design call belongs to the maintainers.Shanmukh-l33t commentedon Nov 4, 2026
The minimal patch you sketched is the shape I had already built and tested before this thread picked up: branch
fix/3813-duplicate-template-registrationat0245673802afd6cac3c8e938cd26eec89f862399(compare: main...Shanmukh-l33t:python-sdk:fix/3813-duplicate-template-registration). It adds theexisting is not Nonecheck mirroringadd_resource(resource_manager.py:52-58): warn only whenwarn_on_duplicate_resourcesis set, keep the first registration. 3 tests were red atmainand green with the fix.Your point about the discarded return is exactly why I stopped at warn-and-keep-first instead of raising:
MCPServer.resource()callsadd_template(...)as a bare statement (server.py:905) and hands the author their own function back, so both today's silent overwrite and a warn-only fix leave a dead handler registered — the decorator has no channel to the author at import time except raising.So on the raise path I'd take your two scoping calls as-is: one issue covering
add_resourceandadd_templatetogether (otherwise the two surfaces just diverge the other way), and a named exception instead of bareValueError— authors today get a warning and a working handler, so an undifferentiatedValueErrorout of their own decorator call reads like a bug in their code. The register-twice → assert-exception → assert-first-handler-still-resolves test shape ports straight onto what the warn-path tests already do.The
uri_templateequivalence gap you flagged (file://doc/{id}andfile://doc/{name}both surviving) is a deeper question — pattern-equivalence dedupe isn't string equality — and I'd keep it out of the literal-duplicate fix entirely rather than block it.Either way the design call is the maintainers'; the branch is there as the minimal fix or a starting point.
(Written with AI assistance; line references re-verified against
maintoday.)Tanmay9223 commentedon Nov 4, 2026
Thank you @Shanmukh-l33t and @media-ctrl for the thorough reproduction, analysis, and discussion!
I completely agree with the consensus on scoping:
Keep [Bug] ResourceManager.add_template() ignores warn_on_duplicate_resources and silently overwrites existing templates #3813 strictly focused on the literal duplicate bug:
Aligning
ResourceManager.add_template()to mirroradd_resource()(consultingwarn_on_duplicate_resources, logging the warning when enabled, and returning the existing template without overwriting) resolves the exact inconsistency reported here with zero collateral risk.Track broader architectural discussions separately:
DuplicateResourceError) is a cross-cutting design call that should span bothadd_resourceandadd_templateequally so the surfaces don't diverge.{id}vs{name}) requires formal pattern parsing and precedence rules, which definitely belongs in its own feature/SEP discussion.As the reporter, I'm happy to open the minimal PR for this (and ensure @Shanmukh-l33t is co-authored for the test cases and prototype) if maintainers would like an outside contribution, or happy if the maintainer team prefers to pick it up directly.
(AI disclosure: drafted with AI assistance and reviewed by me.)
Shanmukh-l33t commentedon Nov 4, 2026
Agreed on all three scoping points — your summary matches exactly where the thread landed: literal-duplicate fix now, raise-on-duplicate as one cross-cutting call spanning both adders (named exception included), pattern equivalence in its own SEP.
The reporter opening the minimal PR is the right call — first call on your own issue — so I'll stay out of the way. The minimal fix is on my fork as
fix/3813-duplicate-template-registration(commit0245673802afd6cac3c8e938cd26eec89f862399): a 5-lineadd_resourcemirror inadd_templateplus 3 warn-flag tests that were red atmainand green with it. Cherry-pick it, re-derive it, whatever is easier — and co-author credit is generous of you but not owed; the PR should land under your name either way.For the raise-path follow-up, the test shape from the branch ports directly: register twice → assert the named exception → assert the first handler still resolves. Happy to review either follow-up when it lands.
(Written with AI assistance.)