MCPToolSupport
Reference
MCPToolSupport (org.xwiki.contrib.llm.mcp.MCPToolSupport, annotated @Unstable) is the declarative parameter layer of the MCP server. A tool declares its parameters once and gets back both the advertised MCP input schema and the typed accessors that read them, so the schema and the parsing code cannot drift apart.
An instance is created with MCPToolSupport.builder() and held in a static final field, which puts the failure mode of a broken declaration at class initialization.
private static final MCPToolSupport PARAMS = MCPToolSupport.builder()
.requiredString("reference", "The document to read, e.g. \"Sandbox.WebHome\".")
.string("locale", "Read a translation, e.g. " + MCPToolSupport.LOCALE_FORMS + ".")
.integer("limit", "Revisions per page (default 20, max 100).")
.bool("showHidden", "Include hidden pages (default false).")
.build();Declaring parameters
Each builder method declares one flat parameter and its agent-facing description. Declaration order is preserved, so the advertised schema lists the parameters in the order the tool chose.
| Method | Declares |
|---|---|
string(name, description) | An optional string |
requiredString(name, description) | A required string |
stringIf(condition, name, description) | An optional string, only when the condition holds |
integer(name, description) | An optional integer |
requiredInteger(name, description) | A required integer |
bool(name, description) | An optional boolean |
stringArray(name, description) | An optional flat array of strings |
stringMap(name, description) | An optional flat object whose values are strings |
requiredStringMap(name, description) | The same, required |
build() | The finished parameter set |
Nested object parameters are out of scope by design: a tool declares its scalars, flat string arrays and flat string maps here, and merges any bespoke schema part through inputSchema(Map).
Generating the schema
| Method | Returns |
|---|---|
inputSchema() | The JSON Schema 2020-12 object map that McpSchema.Tool.builder(String, Map) expects |
inputSchema(Map extraProperties) | The same, merged with hand-built properties, for the rare non-scalar parameter |
@Override
public McpSchema.Tool getToolDefinition()
{
return McpSchema.Tool.builder(TOOL_ID, PARAMS.inputSchema())
.description("Read a document's revision history.")
.build();
}Reading arguments
Every accessor takes the call's argument map and the parameter name.
| Method | Returns |
|---|---|
string(args, key) | The trimmed value, or null when absent or blank |
stringOrEmpty(args, key) | The value, distinguishing present-but-empty from absent, for a parameter where clearing is meaningful |
requireString(args, key) | The value, refusing an absent or blank one |
integer(args, key) | The value, or null when absent |
integer(args, key, defaultValue) | The value, or the default when absent |
requireInteger(args, key) | The value, refusing an absent one |
bool(args, key) | The value, false when absent |
boolOrNull(args, key) | The value as a tri-state, null when absent, for an omitted-means-unchanged flag |
stringList(args, key) | The array's elements, refusing a non-string element |
stringMap(args, key) | The object's entries, refusing a non-string value |
requireStringMap(args, key) | The same, refusing an absent one |
A type mismatch throws IllegalArgumentException carrying an agent-facing message, which the tool turns into an error result. Reading a parameter that was never declared, or declared with another type, throws IllegalStateException: that is a programmer error, and the tool's own tests are where it is meant to surface.
Static helpers
| Member | Purpose |
|---|---|
result(message) | A successful text result |
errorResult(message) | An error text result (isError=true) |
stripLineBreaks(value) | Removes every control, line-separator and bidirectional formatting character, so untrusted page text can neither break a line of the output grammar nor reorder what it renders as |
parseLocale(raw, key) | Parses and validates a locale argument, with the message shared by every locale-aware tool |
isoInstant(value) | Formats an untyped date value as an ISO-8601 UTC instant, the unambiguous form for agent-facing output |
booleanValue(value, key) | Coerces an already-extracted value to a boolean, for a flag nested inside a bespoke argument |
ERROR_PREFIX | The prefix the parameter errors share, for a tool building its own |
LOCALE_FORMS | The example locale forms, shared by the descriptions and the parse error |
Schemas that vary with cross-wiki reach
A tool whose advertised schema depends on cross-wiki reach holds both variants in MCPReachAwareParams rather than re-implementing the split. Parsing always uses the superset; only the advertised schema differs.
private static final MCPReachAwareParams PARAMS = MCPReachAwareParams.of(MyTool::params);
private static MCPToolSupport params(boolean crossWiki)
{
return MCPToolSupport.builder()
.requiredString("reference", "The document to read.")
.stringIf(crossWiki, "wiki", "Optional wiki id to read from instead of the current wiki.")
.build();
}| Member | Purpose |
|---|---|
of(paramsBuilder) | Builds both variants eagerly, from apply(true) and apply(false) |
advertised(reachEnabled) | The variant to put in the tool definition |
parser() | The superset, always used to read arguments |
CROSS_WIKI_REFERENCE_SENTENCE | The sentence the cross-wiki variants append to a reference description |
FAQ
Why not just write the JSON schema by hand?
Because then it exists twice: once as the advertised schema and once as the parsing code, and nothing fails when they disagree. The agent is told about a parameter the tool no longer reads, or sends one the schema never mentioned.
Can a tool declare a nested object parameter?
Not through the builder. Declare the scalars, arrays and string maps here and merge the bespoke part with inputSchema(Map), keeping the hand-written schema down to the part that needs it.