Skip to content

feat(search): replace sort clause maps with a typed SortClause record - #164

Open
adityamparikh wants to merge 1 commit into
apache:mainfrom
adityamparikh:feat/typed-sort-clauses
Open

feat(search): replace sort clause maps with a typed SortClause record#164
adityamparikh wants to merge 1 commit into
apache:mainfrom
adityamparikh:feat/typed-sort-clauses

Conversation

@adityamparikh

Copy link
Copy Markdown
Contributor

Motivation

The search tool's sortClauses parameter is List<Map<String, String>> with magic "item"/"order" keys. A JSON schema cannot express required keys of a map, so the tool schema MCP clients receive says nothing about what to send — LLM clients guess (field/direction is a common wrong guess) and sorting silently doesn't apply (map.get(...) returns null).

A typed record turns the keys into named schema properties: the contract becomes machine-checkable instead of guessable.

Changes

  • New SortClause(field, order) record in the search package; sortClauses is now List<SortClause>.
  • LLM-friendly normalization at conversion time: order is case-insensitive (DESC works) and defaults to asc when omitted; an unrecognized order or missing field throws IllegalArgumentException naming the offending value, so the client can self-correct on the next call.
  • Removed the now-unused SORT_ITEM/SORT_ORDER constants.
  • Registered the record in SolrNativeHints (tool parameter records are deserialized reflectively by Jackson, same as response records; list renamed MCP_RESPONSE_RECORDSMCP_TOOL_RECORDS).
  • Tests: new SortClauseTest covering conversion, case normalization, default order, and both rejection paths; existing unit + integration tests updated to the record.

Note: this changes the tool's parameter schema. Map-shaped calls from existing clients would stop matching — but they only "worked" if the client guessed both magic keys correctly, which the schema never told them.

./gradlew build passes (unit + Testcontainers integration tests).

🤖 Generated with Claude Code

The search tool's sortClauses parameter was List<Map<String,String>>
keyed by magic "item"/"order" strings that the generated JSON schema
cannot express, so MCP clients had to guess the keys. A record makes
the components named schema properties and the contract self-documenting.

The record also normalizes input the way LLM clients actually send it:
order is case-insensitive and defaults to asc when omitted; an unknown
order or missing field fails with a message that names the offending
value so clients can self-correct on retry.

Registered in SolrNativeHints alongside the response records: tool
parameter records are bound reflectively by Jackson and need the same
native-image treatment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: adityamparikh <aditya.m.parikh@gmail.com>
@adityamparikh
adityamparikh force-pushed the feat/typed-sort-clauses branch from 7fad8e3 to 8746845 Compare August 19, 2026 11:58
@adityamparikh

Copy link
Copy Markdown
Contributor Author

Rebased onto main (a84033b). This one needed real conflict resolution plus a compile fix, so flagging what changed beyond history:

1. SolrQuery moved package (this branch did not compile without it). SolrJ 10 relocated SolrQuery from org.apache.solr.client.solrj to org.apache.solr.client.solrj.request. This branch was written against SolrJ 9, so SortClause.java and SortClauseTest.java both had the old import and failed compileJava outright after the rebase. Fixed in both.

2. Reconciled with #176's map validation. main added SearchService.toSortClause(Map) plus SORT_ITEM/SORT_ORDER constants, which is the same problem this PR solves with a type. I removed the map path (constants + private helper) and kept the record, since carrying both would leave two validators for one parameter. Everything else #166 added to SearchService — the remediation-hint constants, the @Nullable parameters, the eDisMax local-params text from #161 — is preserved.

3. Updated two test call sites that did not exist when this PR was written. SearchServiceIntegrationTest and SearchServiceTest gained SearchService.SORT_ITEM/SORT_ORDER usages via #166/#176. Git merged them cleanly because they are on different lines; only javac caught them. Both now construct new SortClause(field, order).

4. SolrNativeHints: kept main's removal of FieldStats (#180) and added SortClause — tool parameter records are Jackson-bound reflectively, so they need the same treatment as response records.

Verified: ./gradlew build green on JDK 25.

adityamparikh added a commit to adityamparikh/solr-mcp that referenced this pull request Aug 20, 2026
The note quoted the sort clause shape as {"item": ..., "order": ...}. apache#164
replaces that map with a typed SortClause record keyed on "field", so the
example would have become wrong the moment that PR merged — and this tutorial
already carries one merge-order dependency.

The underlying caveat holds either way: undeclared arguments are dropped rather
than rejected, so a misnamed one reads as a query that quietly ignored part of
the request. Keeps that, drops the key names, and points at the client's tool
inspector as the durable way to check.

Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
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