Skip to content

Fix NPE when adding or removing features on a server with derived capabilities - #1178

Merged
Kehrlann merged 1 commit into
modelcontextprotocol:mainfrom
itzjb:fix/derived-capabilities-listchanged-npe
Oct 13, 2026
Merged

Kehrlann merged 1 commit into
modelcontextprotocol:mainfrom
itzjb:fix/derived-capabilities-listchanged-npe

Conversation

@itzjb

@itzjb itzjb commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #1177

Motivation and Context

If you build a server without .capabilities(...) and register tools, resources or prompts on the builder, McpServerFeatures derives the capabilities with ToolCapabilities.builder().build() (and the resource/prompt equivalents). Since #928 those builders leave listChanged as null; before #928 the derived value was false.

McpAsyncServer unboxes that Boolean in seven places:

if (this.serverCapabilities.tools().listChanged()) {

so addTool, removeTool, addResource, removeResource, addResourceTemplate, addPrompt and removePrompt fail with a NullPointerException when the returned Mono is subscribed. The tool, resource or prompt has already been added or removed at that point, so the caller sees an error for a change that took effect. McpSyncServer rethrows the NPE from .block().

McpSyncServer server = McpServer.sync(transportProvider)
    .serverInfo("server", "1.0.0")
    .tools(toolA)
    .build();
server.addTool(toolB); // NullPointerException

This change compares with Boolean.TRUE.equals(...), which McpAsyncServer already does for resources().subscribe(). A null listChanged now means "no list_changed notification", which matches the capability that was advertised.

How Has This Been Tested?

Added DerivedCapabilitiesRuntimeChangesTests (mcp-test). It builds async and sync servers without explicit capabilities and checks that runtime add/remove of tools, resources, resource templates and prompts complete and send no notification. All four tests fail on main with the NPE and pass with this change.

./mvnw -pl mcp-test -am test -Dtest=DerivedCapabilitiesRuntimeChangesTests,StdioMcpAsyncServerTests,StdioMcpSyncServerTests,ResourceTemplateManagementTests,ResourceSubscriptionTests,HttpServletStreamableAsyncServerTests,HttpServletStreamableSyncServerTests -Dsurefire.failIfNoSpecifiedTests=false

Breaking Changes

None. Servers that set listChanged explicitly behave as before.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Restoring listChanged(false) in McpServerFeatures would also fix the default path, but it would bring back "listChanged": false on the wire, which #928 moved away from. It would also leave ServerCapabilities.builder().tools(null) failing the same way. McpAsyncClient unboxes roots().listChanged() the same way, but its derived default is false, so I left it out of this PR.

@itzjb
itzjb marked this pull request as ready for review October 13, 2026 04:56
…abilities

Derived server capabilities leave listChanged null since modelcontextprotocol#928.
Compare with Boolean.TRUE.equals in McpAsyncServer so runtime
add/remove of tools, resources, resource templates and prompts
completes instead of throwing a NullPointerException.
@Kehrlann
Kehrlann force-pushed the fix/derived-capabilities-listchanged-npe branch from d82a00f to 6a8140d Compare October 13, 2026 15:58
@Kehrlann
Kehrlann self-requested a review October 13, 2026 16:00
@Kehrlann
Kehrlann merged commit 1f3bdfe into modelcontextprotocol:main Oct 13, 2026
22 of 25 checks passed
Kehrlann added a commit that referenced this pull request Oct 13, 2026
- Follow-up to #1178

Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

addTool/removeTool throw NullPointerException when server capabilities are derived from registered features

2 participants