Skip to content

[Bug] ResourceManager.add_template() ignores warn_on_duplicate_resources and silently overwrites existing templates #3813

Open

Description

@Tanmay9223

Initial Checks

Release line

2.x (current stable)

Description

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:

  1. It does not check self.warn_on_duplicate_resources.
  2. It emits no log warning when an identical URI template is re-registered.
  3. 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

import logging
from mcp.server.mcpserver import MCPServer

logging.basicConfig(level=logging.WARNING)

server = MCPServer(warn_on_duplicate_resources=True)

# 1. Static resources: logs warning and preserves existing (doc1)
@server.resource("file://doc")
def doc1() -> str:
    return "v1"

@server.resource("file://doc")
def doc2() -> str:
    return "v2"

# 2. Template resources: emits no warning and silently overwrites template1 with template2
@server.resource("file://doc/{id}")
def template1(id: str) -> str:
    return "template_v1"

@server.resource("file://doc/{id}")
def template2(id: str) -> str:
    return "template_v2"

# Static resource warned and kept doc1:
assert server._resource_manager._resources["file://doc"].fn() == "v1"

# Template resource silently overwritten without warning:
assert server._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.

Python & MCP Python SDK

  • Python 3.11 / 3.12 / 3.14
  • MCP Python SDK: main branch (v2.x)

Activity

  1. added
    bugSomething isn't working
    v1Affects the v1.x maintenance line
    v2Affects the v2 line (2.x on main)
    on Nov 3, 2026
  2. Shanmukh-l33t commented on Nov 3, 2026

    @Shanmukh-l33t

    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.

    Fix shape (mirror add_resource() exactly):

    existing = self._templates.get(template.uri_template)
    if existing:
        if self.warn_on_duplicate_resources:
            logger.warning(f"Template already exists: {template.uri_template}")
        return existing
    self._templates[template.uri_template] = template
    return template

    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.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingv1Affects the v1.x maintenance linev2Affects the v2 line (2.x on main)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions