Wiki source code of MCPToolSupport
Last modified by Vincent Massol on 2026/09/04 19:23
Show last authors
| author | version | line-number | content |
|---|---|---|---|
| 1 | {{code}}MCPToolSupport{{/code}} ({{code}}org.xwiki.contrib.llm.mcp.MCPToolSupport{{/code}}, annotated {{code}}@Unstable{{/code}}) 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. | ||
| 2 | |||
| 3 | An instance is created with {{code}}MCPToolSupport.builder(){{/code}} and held in a {{code}}static final{{/code}} field, which puts the failure mode of a broken declaration at class initialization. | ||
| 4 | |||
| 5 | {{code language="java"}} | ||
| 6 | private static final MCPToolSupport PARAMS = MCPToolSupport.builder() | ||
| 7 | .requiredString("reference", "The document to read, e.g. \"Sandbox.WebHome\".") | ||
| 8 | .string("locale", "Read a translation, e.g. " + MCPToolSupport.LOCALE_FORMS + ".") | ||
| 9 | .integer("limit", "Revisions per page (default 20, max 100).") | ||
| 10 | .bool("showHidden", "Include hidden pages (default false).") | ||
| 11 | .build(); | ||
| 12 | {{/code}} | ||
| 13 | |||
| 14 | == Declaring parameters == | ||
| 15 | |||
| 16 | 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. | ||
| 17 | |||
| 18 | |=Method |=Declares | ||
| 19 | |{{code}}string(name, description){{/code}} |An optional string | ||
| 20 | |{{code}}requiredString(name, description){{/code}} |A required string | ||
| 21 | |{{code}}stringIf(condition, name, description){{/code}} |An optional string, only when the condition holds | ||
| 22 | |{{code}}integer(name, description){{/code}} |An optional integer | ||
| 23 | |{{code}}requiredInteger(name, description){{/code}} |A required integer | ||
| 24 | |{{code}}bool(name, description){{/code}} |An optional boolean | ||
| 25 | |{{code}}stringArray(name, description){{/code}} |An optional flat array of strings | ||
| 26 | |{{code}}stringMap(name, description){{/code}} |An optional flat object whose values are strings | ||
| 27 | |{{code}}requiredStringMap(name, description){{/code}} |The same, required | ||
| 28 | |{{code}}build(){{/code}} |The finished parameter set | ||
| 29 | |||
| 30 | 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 {{code}}inputSchema(Map){{/code}}. | ||
| 31 | |||
| 32 | == Generating the schema == | ||
| 33 | |||
| 34 | |=Method |=Returns | ||
| 35 | |{{code}}inputSchema(){{/code}} |The JSON Schema 2020-12 object map that {{code}}McpSchema.Tool.builder(String, Map){{/code}} expects | ||
| 36 | |{{code}}inputSchema(Map extraProperties){{/code}} |The same, merged with hand-built properties, for the rare non-scalar parameter | ||
| 37 | |||
| 38 | {{code language="java"}} | ||
| 39 | @Override | ||
| 40 | public McpSchema.Tool getToolDefinition() | ||
| 41 | { | ||
| 42 | return McpSchema.Tool.builder(TOOL_ID, PARAMS.inputSchema()) | ||
| 43 | .description("Read a document's revision history.") | ||
| 44 | .build(); | ||
| 45 | } | ||
| 46 | {{/code}} | ||
| 47 | |||
| 48 | == Reading arguments == | ||
| 49 | |||
| 50 | Every accessor takes the call's argument map and the parameter name. | ||
| 51 | |||
| 52 | |=Method |=Returns | ||
| 53 | |{{code}}string(args, key){{/code}} |The trimmed value, or {{code}}null{{/code}} when absent or blank | ||
| 54 | |{{code}}stringOrEmpty(args, key){{/code}} |The value, distinguishing present-but-empty from absent, for a parameter where clearing is meaningful | ||
| 55 | |{{code}}requireString(args, key){{/code}} |The value, refusing an absent or blank one | ||
| 56 | |{{code}}integer(args, key){{/code}} |The value, or {{code}}null{{/code}} when absent | ||
| 57 | |{{code}}integer(args, key, defaultValue){{/code}} |The value, or the default when absent | ||
| 58 | |{{code}}requireInteger(args, key){{/code}} |The value, refusing an absent one | ||
| 59 | |{{code}}bool(args, key){{/code}} |The value, {{code}}false{{/code}} when absent | ||
| 60 | |{{code}}boolOrNull(args, key){{/code}} |The value as a tri-state, {{code}}null{{/code}} when absent, for an omitted-means-unchanged flag | ||
| 61 | |{{code}}stringList(args, key){{/code}} |The array's elements, refusing a non-string element | ||
| 62 | |{{code}}stringMap(args, key){{/code}} |The object's entries, refusing a non-string value | ||
| 63 | |{{code}}requireStringMap(args, key){{/code}} |The same, refusing an absent one | ||
| 64 | |||
| 65 | A type mismatch throws {{code}}IllegalArgumentException{{/code}} 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 {{code}}IllegalStateException{{/code}}: that is a programmer error, and the tool's own tests are where it is meant to surface. | ||
| 66 | |||
| 67 | == Static helpers == | ||
| 68 | |||
| 69 | |=Member |=Purpose | ||
| 70 | |{{code}}result(message){{/code}} |A successful text result | ||
| 71 | |{{code}}errorResult(message){{/code}} |An error text result ({{code}}isError=true{{/code}}) | ||
| 72 | |{{code}}stripLineBreaks(value){{/code}} |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 | ||
| 73 | |{{code}}parseLocale(raw, key){{/code}} |Parses and validates a {{code}}locale{{/code}} argument, with the message shared by every locale-aware tool | ||
| 74 | |{{code}}isoInstant(value){{/code}} |Formats an untyped date value as an ISO-8601 UTC instant, the unambiguous form for agent-facing output | ||
| 75 | |{{code}}booleanValue(value, key){{/code}} |Coerces an already-extracted value to a boolean, for a flag nested inside a bespoke argument | ||
| 76 | |{{code}}ERROR_PREFIX{{/code}} |The prefix the parameter errors share, for a tool building its own | ||
| 77 | |{{code}}LOCALE_FORMS{{/code}} |The example locale forms, shared by the descriptions and the parse error | ||
| 78 | |||
| 79 | == Schemas that vary with cross-wiki reach == | ||
| 80 | |||
| 81 | A tool whose advertised schema depends on cross-wiki reach holds both variants in {{code}}MCPReachAwareParams{{/code}} rather than re-implementing the split. Parsing always uses the superset; only the advertised schema differs. | ||
| 82 | |||
| 83 | {{code language="java"}} | ||
| 84 | private static final MCPReachAwareParams PARAMS = MCPReachAwareParams.of(MyTool::params); | ||
| 85 | |||
| 86 | private static MCPToolSupport params(boolean crossWiki) | ||
| 87 | { | ||
| 88 | return MCPToolSupport.builder() | ||
| 89 | .requiredString("reference", "The document to read.") | ||
| 90 | .stringIf(crossWiki, "wiki", "Optional wiki id to read from instead of the current wiki.") | ||
| 91 | .build(); | ||
| 92 | } | ||
| 93 | {{/code}} | ||
| 94 | |||
| 95 | |=Member |=Purpose | ||
| 96 | |{{code}}of(paramsBuilder){{/code}} |Builds both variants eagerly, from {{code}}apply(true){{/code}} and {{code}}apply(false){{/code}} | ||
| 97 | |{{code}}advertised(reachEnabled){{/code}} |The variant to put in the tool definition | ||
| 98 | |{{code}}parser(){{/code}} |The superset, always used to read arguments | ||
| 99 | |{{code}}CROSS_WIKI_REFERENCE_SENTENCE{{/code}} |The sentence the cross-wiki variants append to a reference description |