- Rule: R005
- Fixer: R005,
safeconfidence -- addsextensions={}to aServerCapabilities(...)call that doesn't already have one; this is a pure no-op at runtime since an absent value already meant "no extensions." - Severity: advisory
- Spec: "extensions field on ServerCapabilities" -- https://modelcontextprotocol.io/specification/draft/changelog
Optional capabilities now negotiate through an extensions map on
ServerCapabilities rather than being assumed absent. Declaring
capabilities without it doesn't break anything today -- a 2026-07-28 client
just sees no extensions and moves on -- but it also tells the client
nothing about which 2026-07-28-era extensions (like
io.modelcontextprotocol/tasks, see recipe 11) this
server does or doesn't support.
from mcp.server import Server
from mcp.types import ServerCapabilities, ToolsCapability
server = Server("notes-server")
capabilities = ServerCapabilities(
tools=ToolsCapability(list_changed=True),
)The no-op case -- the server doesn't implement any 2026-07-28 extension, but says so explicitly rather than leaving the client to guess:
from mcp.server import Server
from mcp.types import ServerCapabilities, ToolsCapability
server = Server("notes-server")
capabilities = ServerCapabilities(
tools=ToolsCapability(list_changed=True),
extensions={},
)This is exactly what mcp-migrate fix produces -- the fixer only ever adds
extensions={} to an existing construction, at safe confidence, because
an absent value already meant "no extensions" and this makes that explicit
without changing behavior.
The other case -- a server that does implement one, such as Tasks after moving out of core capabilities:
capabilities = ServerCapabilities(
tools=ToolsCapability(list_changed=True),
extensions={"io.modelcontextprotocol/tasks": {}},
)The R005 fixer never produces this form on its own -- populating an
extension with real config, rather than an empty placeholder, is a
decision about what the server actually supports, not a mechanical edit.
R019's fixer reflects
the same limit from the other direction: it comments out the removed
tasks/list/blocking tasks/result call sites and leaves a TODO pointing
at recipe 11, at review confidence, rather than
guessing at the
extensions={"io.modelcontextprotocol/tasks": {}} declaration itself --
that part still needs a human.
- The rule (R005) only fires when it sees an actual
ServerCapabilities(...)call missingextensions-- it will not tell you which extensions to add, only that the map itself is absent. Addingextensions={}silences the finding whether or not the server should really be declaring something under it. - If your project declares capabilities in more than one place (a
factoryfunction, a test helper, a real server construction), the rule checks each file that referencesServerCapabilitiesindependently -- fixing one call site does not silence a separate one in another file. - An empty
extensions={}and noextensionskey at all mean the same thing to a 2026-07-28 client today. The value of adding it now is forward compatibility: it tells a reader (and eventually a stricter client) that this server has been reviewed against the current spec, not that it predates it.
https://modelcontextprotocol.io/specification/draft/changelog