This is an automated email from the ASF dual-hosted git repository.
epugh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr-mcp.git
The following commit(s) were added to refs/heads/main by this push:
new f01cfff feat(indexing): report indexed field names in tool responses
(#165)
f01cfff is described below
commit f01cfff7322690766a86e3d3a4d399bbb75972e0
Author: Aditya Parikh <[email protected]>
AuthorDate: Tue Aug 18 17:02:22 2026 -0400
feat(indexing): report indexed field names in tool responses (#165)
Document creators sanitize input field names for Solr compatibility
(User-Name becomes user_name), but nothing told the MCP client, which
then queries the original names and silently gets zero matches. The
index-json/csv/xml-documents responses now list the distinct field
names as indexed (capped at 50, remainder counted), and each tool
description states that sanitization happens.
Signed-off-by: adityamparikh <[email protected]>
Co-authored-by: Claude Fable 5 <[email protected]>
Co-authored-by: Eric Pugh <[email protected]>
---
.../solr/mcp/server/indexing/IndexingService.java | 52 +++++++++++++++++++---
.../indexing/IndexingServiceIntegrationTest.java | 21 +++++++++
2 files changed, 67 insertions(+), 6 deletions(-)
diff --git
a/src/main/java/org/apache/solr/mcp/server/indexing/IndexingService.java
b/src/main/java/org/apache/solr/mcp/server/indexing/IndexingService.java
index 881e7b1..3467485 100644
--- a/src/main/java/org/apache/solr/mcp/server/indexing/IndexingService.java
+++ b/src/main/java/org/apache/solr/mcp/server/indexing/IndexingService.java
@@ -19,6 +19,9 @@ package org.apache.solr.mcp.server.indexing;
import io.micrometer.observation.annotation.Observed;
import java.io.IOException;
import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
@@ -204,14 +207,16 @@ public class IndexingService {
@McpTool(
name = "index-json-documents",
annotations = @McpTool.McpAnnotations(idempotentHint =
true),
- description = "Index documents from json String into
Solr collection")
+ description = "Index documents from json String into
Solr collection. Field names are"
+ + " sanitized for Solr compatibility
(lowercased, special characters replaced"
+ + " with underscores); the response
lists the field names as indexed")
public String indexJsonDocuments(@McpToolParam(description = "Solr
collection to index into") String collection,
@McpToolParam(description = "JSON string containing
documents to index") String json)
throws IOException, SolrServerException {
List<SolrInputDocument> schemalessDoc =
indexingDocumentCreator.createSchemalessDocumentsFromJson(json);
int successCount = indexDocuments(collection, schemalessDoc);
return "Successfully indexed " + successCount + " of " +
schemalessDoc.size() + " documents into collection '"
- + collection + "'";
+ + collection + "'" +
describeIndexedFields(schemalessDoc);
}
/**
@@ -277,14 +282,16 @@ public class IndexingService {
@McpTool(
name = "index-csv-documents",
annotations = @McpTool.McpAnnotations(idempotentHint =
true),
- description = "Index documents from CSV string into
Solr collection")
+ description = "Index documents from CSV string into
Solr collection. Column names are"
+ + " sanitized for Solr compatibility
(lowercased, special characters replaced"
+ + " with underscores); the response
lists the field names as indexed")
public String indexCsvDocuments(@McpToolParam(description = "Solr
collection to index into") String collection,
@McpToolParam(description = "CSV string containing
documents to index") String csv)
throws IOException, SolrServerException {
List<SolrInputDocument> schemalessDoc =
indexingDocumentCreator.createSchemalessDocumentsFromCsv(csv);
int successCount = indexDocuments(collection, schemalessDoc);
return "Successfully indexed " + successCount + " of " +
schemalessDoc.size() + " documents into collection '"
- + collection + "'";
+ + collection + "'" +
describeIndexedFields(schemalessDoc);
}
/**
@@ -374,14 +381,16 @@ public class IndexingService {
@McpTool(
name = "index-xml-documents",
annotations = @McpTool.McpAnnotations(idempotentHint =
true),
- description = "Index documents from XML string into
Solr collection")
+ description = "Index documents from XML string into
Solr collection. Element names are"
+ + " sanitized for Solr compatibility
(lowercased, special characters replaced"
+ + " with underscores); the response
lists the field names as indexed")
public String indexXmlDocuments(@McpToolParam(description = "Solr
collection to index into") String collection,
@McpToolParam(description = "XML string containing
documents to index") String xml)
throws ParserConfigurationException, SAXException,
IOException, SolrServerException {
List<SolrInputDocument> schemalessDoc =
indexingDocumentCreator.createSchemalessDocumentsFromXml(xml);
int successCount = indexDocuments(collection, schemalessDoc);
return "Successfully indexed " + successCount + " of " +
schemalessDoc.size() + " documents into collection '"
- + collection + "'";
+ + collection + "'" +
describeIndexedFields(schemalessDoc);
}
/**
@@ -448,6 +457,37 @@ public class IndexingService {
* @see SolrClient#add(String, java.util.Collection)
* @see SolrClient#commit(String)
*/
+ /**
+ * Maximum number of distinct field names listed in an indexing
response before
+ * the remainder is elided.
+ */
+ private static final int MAX_REPORTED_FIELDS = 50;
+
+ /**
+ * Summarizes the field names that were actually indexed. Document
creators
+ * sanitize input field names for Solr compatibility (lowercasing,
replacing
+ * special characters with underscores), so the indexed names can
differ from
+ * the input; reporting them lets MCP clients query the right fields
instead of
+ * assuming the input names survived.
+ *
+ * @param documents
+ * the documents that were submitted for indexing
+ * @return a sentence listing the distinct indexed field names, or an
empty
+ * string if there are none
+ */
+ private static String describeIndexedFields(List<SolrInputDocument>
documents) {
+ Set<String> fieldNames = documents.stream().flatMap(document ->
document.getFieldNames().stream())
+ .collect(Collectors.toCollection(TreeSet::new));
+ if (fieldNames.isEmpty()) {
+ return "";
+ }
+ String listed =
fieldNames.stream().limit(MAX_REPORTED_FIELDS).collect(Collectors.joining(",
"));
+ String elided = fieldNames.size() > MAX_REPORTED_FIELDS
+ ? " and " + (fieldNames.size() -
MAX_REPORTED_FIELDS) + " more"
+ : "";
+ return ". Indexed field names (input names are sanitized for
Solr compatibility): " + listed + elided;
+ }
+
public int indexDocuments(String collection, List<SolrInputDocument>
documents)
throws SolrServerException, IOException {
int successCount = 0;
diff --git
a/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceIntegrationTest.java
b/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceIntegrationTest.java
index 933e668..daa9561 100644
---
a/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceIntegrationTest.java
+++
b/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceIntegrationTest.java
@@ -157,6 +157,27 @@ class IndexingServiceIntegrationTest {
assertEquals("test", doc.getFieldValue("genre_s"));
}
+ @Test
+ void indexJsonDocuments_reportsSanitizedFieldNames() throws Exception {
+ String json = """
+ [
+ {
+ "id": "sanitize001",
+ "User-Name": "Jane Doe",
+ "product.price": 9.99
+ }
+ ]
+ """;
+
+ String result =
indexingService.indexJsonDocuments(COLLECTION_NAME, json);
+
+ // The response must list the names as indexed, not as
submitted, so MCP
+ // clients query the fields that actually exist.
+ assertTrue(result.contains("user_name"));
+ assertTrue(result.contains("product_price"));
+ assertFalse(result.contains("User-Name"));
+ }
+
@Test
void testIndexJsonDocuments() throws Exception {