Skip to content

Add McpServerTool shortcuts for registering tools on the modern server - #1271

Closed
bhuvnsh wants to merge 2 commits into
modelcontextprotocol:mainfrom
bhuvnsh:issue-1200-tool-shortcuts-example
Closed

bhuvnsh wants to merge 2 commits into
modelcontextprotocol:mainfrom
bhuvnsh:issue-1200-tool-shortcuts-example

Conversation

@bhuvnsh

@bhuvnsh bhuvnsh commented Oct 24, 2026 •

Copy link
Copy Markdown

Part of #1200. Tools only for now.

Motivation and Context

Today, adding a single tool to the modern server means writing a whole McpSyncToolRepository (a name map, list/find/call, cursor handling), wrapping it in ToolsFeature.ofSync(...) and registering that feature. That's a lot of ceremony for what is usually one function. ModernConformanceServlet carries a fair bit of boilerplate for the same reason.

This adds a shortcut for the common case:

McpServer.builder()
    .serverInfo(info)
    .tool(McpServerTool.sync(greet, (ctx, request) -> greet(request)))
    .tool(McpServerTool.syncStreaming(count, (ctx, request, notifier) -> count(notifier)))
    .build();

McpServerTool is a Tool plus its handler, with sync, syncStreaming, async and asyncStreaming factories. The builder collects the tools into a small package-private in-memory repository and serves it through the regular ToolsFeature. Input/output schema validation and everything else in the feature still applies. If you need the same thing as an explicit feature, for example to set cache hints, there's ToolsFeature.ofTools(...).

A few mistakes now fail when you register the tool instead of when a request comes in:

  • duplicate tool names;
  • invalid tool names, checked by ToolNameValidator as the legacy builder does;
  • mixing tool(...) with another feature that serves tools/*. The error names tool(...) instead of "served by both ToolsFeature and ToolsFeature".

How Has This Been Tested?

I added ToolRegistrationComparisonTests. It registers the same three tools twice, once through a hand-written repository and once through tool(...). One tool is plain, one streams progress, and one asks the user for input. The test checks that both servers answer the same way for:

  • listing and calling tools;
  • schema validation failures;
  • unknown tools and bad cursors;
  • the input-required round trip;
  • progress streaming.

There are also focused tests in McpServerToolTests (the four factories, which thread a sync handler runs on, errors, output schema checks and ofTools) and new builder tests in McpServerTests.

./mvnw -pl mcp-core test passes locally, and so do the modern server tests in mcp-test with both the Jackson 3 and Jackson 2 profiles. I also ran the conformance suite (0.2.0-alpha.12, --requirements 2026-07-28) against the modern conformance server. It exits 0, and the only failures are the tasks-* and http-custom-header-server-validation scenarios, which aren't scored and are already listed in VALIDATION_RESULTS.md. I haven't run the full clean verify.

Breaking Changes

None. The change only adds new API.

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

I kept this to tools on purpose, so the shape can be discussed in #1200 before I do the same for prompts, resources, templates and completions. The conformance server's tools now use the shortcut, so its hand-written tool repository is gone. One small difference there: the old repository ignored a tools/list cursor, and now a cursor is rejected as invalid params, as the repository contract says.

I also left out a .tool(Tool, handler) overload. Sync and async versions with the same lambda shape don't compile side by side (the call is ambiguous), so the builder takes an McpServerTool instead.

Registering one tool on the modern server meant implementing a whole
McpSyncToolRepository, wrapping it in a ToolsFeature and registering the
feature. McpServer.Builder#tool now takes an McpServerTool (a Tool and
its handler) and serves all of them through one ToolsFeature built at
build(), so argument and output validation still apply.

Duplicate and invalid tool names fail at registration, and combining
tool(...) with another feature serving tools/* fails build() with a
message naming the shortcut. ToolsFeature.ofTools(...) serves a fixed
set of tools for users who need custom cache hints or a validator.

Tools only; prompts, resources, templates and completions would follow
the same shape. See modelcontextprotocol#1200.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Document threading, error handling and name validation on McpServerTool,
and give it a toString. Add focused tests for the four handler factories
(including which thread a sync handler runs on), error mapping,
output-schema checks and ToolsFeature.ofTools, plus builder tests for
duplicate names, the tools/* conflict check, the advertised capability
with and without subscriptions, and the builder's schema validator.

ModernConformanceServlet now registers its tools through
McpServer.Builder#tools instead of a hand-written McpSyncToolRepository.
The 2026-07-28 conformance suite passes as before. The one behaviour
change is that a tools/list cursor, which the old repository ignored, is
now rejected as invalid params.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@bhuvnsh bhuvnsh closed this Oct 24, 2026
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.

1 participant