feat(indexing): index-json-documents takes a typed documents array - #202
Merged
Merged
Conversation
The tool took its documents as a JSON string, so the model had to escape every quote and newline of the payload inside a JSON string argument. On a 61-document sample that escaping is about 12% of the output tokens, and a single mis-escaped quote fails the whole call after the payload has been generated. The parameter is now List<Map<String, Object>>, advertised as an array of objects, so the model emits native JSON and the SDK parses it once. JsonDocumentCreator gains a create(List<Map>) entry point that runs the same flattening as the string path; JsonDocumentCreatorTest pins that both produce identical documents. The index-data prompt tells the model to pass the array itself, and its opening line now names the format rather than the parameter. Tests keep their JSON text blocks and parse them through TestDocuments.json. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CiUHyyXLTo9ATdgg8eRFZJ Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CiUHyyXLTo9ATdgg8eRFZJ Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
This was referenced Sep 14, 2026
… points Cleanup pass over the typed-documents change. No behaviour change beyond the markdown prompt fix noted below. JsonDocumentCreator: create(List) was inserted between the create(String) javadoc and the method it documents, so javac bound only the trailing block -- create(String) lost its docs and create(List) inherited a @PARAM json it does not have. Moving it below reunites them. Both entry points now share flatten(JsonNode)/toDocument(JsonNode) instead of maintaining parallel loops, so objectsAndStringProduceTheSameDocuments guards a shared path rather than being the only thing keeping two hand-written loops in step. create(List) converts the whole list with one valueToTree call instead of one per document. Null moves down to the creator, which already owns the "nothing to index" policy, so it reports null the same way it reports empty. That drops the one-off IllegalArgumentException from indexJsonDocuments, which none of the three sibling index tools had. IndexTool gains the canonical format. The prompt's first %s was indexTool.paramName(), which worked only because paramName happened to equal the format keyword; renaming the JSON parameter to `documents` broke that, and re-normalising the raw argument lost the md -> markdown canonicalisation, so format=md rendered "You are indexing md data". Also: createSchemalessDocuments -> createSchemalessDocumentsFromJson, so the orchestrator keeps its ...From<Format> family; the stale @see on indexJsonDocuments; TestDocuments catches JsonProcessingException, which is what readValue declares and what survives Jackson 3, and hoists its TypeReference; and CollectionServiceIntegrationTest passes the documents it already holds instead of serialising and reparsing them, retiring a now-unused autowired ObjectMapper. Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JZoVjWoHU315uEngMQdhWE Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
The typed `documents` array has no string form to measure, so the `index-json-documents` path was already uncapped. Rather than add a second limit to match, remove the one on the string entry point so both JSON paths behave the same way. Payload size is now bounded by the heap and by whatever limit the transport in front of the server imposes, not by this creator. CSV and markdown keep their own caps; this changes JSON only. No test covered the limit, so nothing else moves. Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JZoVjWoHU315uEngMQdhWE Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
Follows the JSON change: all three creators carried the same copy-pasted 10 MB constant and the same getBytes() length check, which materialised a second full copy of the payload purely to measure it, immediately before the parser read the string again. Payload size is now bounded by the heap and by whatever limit the transport in front of the server imposes, not by the creators. The XML path keeps its own "XML document too large" check in IndexingDocumentCreator, which is a separate limit with test coverage. No test covered either cap, so nothing else moves. Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JZoVjWoHU315uEngMQdhWE Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
Completes the removal across all four formats. The XML limit lived in the orchestrator rather than in XmlDocumentCreator, and was the last of the copy-pasted 10 MB checks; like the others it allocated a full byte[] copy of the payload purely to measure it before the parser read the string. Removes MAX_XML_SIZE_BYTES, the check, the now-unused StandardCharsets import, and testCreateSchemalessDocumentsFromXmlWithLargeDocument, which built a 12 MB document to assert the limit fired. The null/blank validation on the XML path is unchanged and still covered. Payload size is now bounded by the heap and by whatever limit the transport in front of the server imposes. Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JZoVjWoHU315uEngMQdhWE Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
This was referenced Sep 14, 2026
Open
adityamparikh
added a commit
to adityamparikh/solr-mcp
that referenced
this pull request
Sep 16, 2026
…103 #104 apache#205) into sb4 Resolves the conflicts against main and ports what merged cleanly but would not compile on the Boot 4 / Jackson 3 line: - gradle/libs.versions.toml: keep the Boot 4 test starters and the mcp-sdk pin, add spring-security-test (apache#187), drop commons-csv (apache#205). - JsonDocumentCreator: take apache#202's flatten()/toDocument() split with Jackson 3 imports and JacksonException. - CollectionServiceIntegrationTest / IndexingServiceIntegrationTest: take main's typed-documents and autowired-creator setup. - TestDocuments, JsonDocumentCreatorTest (new on main): com.fasterxml -> tools.jackson, JsonMapper.builder().build(), JacksonException. - IndexingServiceIntegrationTest: add the assertThrows static import that sb4's explicit-import expansion left out for apache#205's new XML test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
index-json-documentstook its documents as a JSON string, so the model had to escape every quote and newline of the payload inside a JSON string argument. On the 61-show sample that escaping is about 12% of the output tokens the model generates for the call, and one mis-escaped quote fails the whole call after the payload has been produced.The parameter is now
List<Map<String, Object>> documents, advertised in the tool schema as an array of objects. The model emits native JSON, the MCP client parses it once, and on the SDK 2.0 line (#23) the shape is validated before dispatch.Server-side indexing time is unchanged — 61 documents take 0.1 s either way — so the whole saving is in what the model has to emit.
What changed
JsonDocumentCreator.create(List<Map<String,Object>>)converts the list with onevalueToTreecall and hands it to the sameflatten(JsonNode)/toDocument(JsonNode)walkcreate(String)uses, so both entry points flatten identically by construction (nested objects toa_b, arrays to multi-valued fields, nulls skipped).JsonDocumentCreatorTestpins that they produce the same documents.IndexingService.indexJsonDocumentstakes the typed list; the tool description tells the model to pass the array itself, not a string. Empty/null input is rejected by the creator, which already owns that policy for every format.IndexToolin theindex-dataprompt now carries the canonical format name separately from the payload parameter name — the two were the same string until the JSON parameter was renamed todocuments, and the prompt's opening line needs the format ("markdown" even when the caller passesmd), not the parameter.TestDocuments.json(...)helper; the MCP-level tests send the array.Input size limits removed
Behavior change worth a reviewer's attention: the 10 MB input cap is gone from all four formats, not just JSON.
The typed
documentsarray has no string form to measure, so the JSON tool path was already uncapped; adding a second limit to match would have meant maintaining the cap in two shapes. All four formats carried the same copy-pasted10 * 1024 * 1024constant and the samegetBytes()length check, which materialised a second full copy of the payload purely to measure it, immediately before the parser read the string again — in two different layers (JSON/CSV/markdown in the creators, XML in the orchestrator) with two different message formats.Payload size is now bounded by the heap and by whatever limit the transport in front of the server imposes. Blank/null validation is unchanged on every path and still covered. The one test that exercised a limit —
testCreateSchemalessDocumentsFromXmlWithLargeDocument, which built a 12 MB document to assert the XML check fired — is removed with it.Not changed
CSV, XML and Markdown stay strings: those formats have few quotes and no repeated keys, so a string is already their cheapest wire form.
Independent of #203 and #205; the three merge in any order.
Tests
./gradlew build(Java 25) and./gradlew nativeTest -Pnative(GraalVM CE 25.0.2) are green; the genericList<Map>parameter deserialises natively without extra hints.🤖 Generated with Claude Code