diff --git a/build.gradle.kts b/build.gradle.kts
index dc5417bb..6811ac6f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -286,8 +286,9 @@ spotless {
target("src/**/*.java")
// Use Eclipse JDT formatter to avoid google-java-format's incompatibility
// with cutting-edge JDKs (e.g., 25) which can trigger NoSuchMethodError
- // against internal javac classes.
- eclipse()
+ // against internal javac classes. Override only the annotation-argument
+ // alignment so multi-arg @Mcp* annotations render one-arg-per-line.
+ eclipse().configFile("config/spotless/eclipse-java-formatter.properties")
removeUnusedImports()
trimTrailingWhitespace()
endWithNewline()
diff --git a/config/spotless/eclipse-java-formatter.properties b/config/spotless/eclipse-java-formatter.properties
new file mode 100644
index 00000000..f366fba5
--- /dev/null
+++ b/config/spotless/eclipse-java-formatter.properties
@@ -0,0 +1,16 @@
+# Eclipse JDT formatter overrides layered on top of the built-in defaults that
+# Spotless's eclipse() formatter ships with. Only keys listed here override the
+# defaults; everything else stays at Eclipse's stock values.
+#
+# Why: we want every multi-argument annotation (especially @McpTool, @McpPrompt,
+# @McpResource, @McpArg, @McpToolParam) to render with one argument per line so
+# the behavior hints (readOnlyHint / destructiveHint / idempotentHint) and the
+# descriptive copy stay readable at a glance. The default value here is
+# M_COMPACT_SPLIT (16), which only wraps when the line gets too long — leaving
+# many annotations collapsed onto a single 200-column line.
+#
+# Value 48 = M_ONE_PER_LINE_SPLIT (without M_FORCE). The formatter wraps to one
+# argument per line only when the line is too long, so single-arg annotations
+# like @McpToolParam(description = "...") stay on one line. See:
+# org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=48
diff --git a/src/main/java/org/apache/solr/mcp/server/collection/CollectionService.java b/src/main/java/org/apache/solr/mcp/server/collection/CollectionService.java
index 8e314baf..465c1914 100644
--- a/src/main/java/org/apache/solr/mcp/server/collection/CollectionService.java
+++ b/src/main/java/org/apache/solr/mcp/server/collection/CollectionService.java
@@ -23,10 +23,12 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import io.micrometer.observation.annotation.Observed;
+import io.modelcontextprotocol.spec.McpSchema.CompleteRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
+import java.util.Locale;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
@@ -41,7 +43,10 @@
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.mcp.server.config.SolrConfigurationProperties;
+import org.apache.solr.mcp.server.util.PromptNames;
+import org.springaicommunity.mcp.annotation.McpArg;
import org.springaicommunity.mcp.annotation.McpComplete;
+import org.springaicommunity.mcp.annotation.McpPrompt;
import org.springaicommunity.mcp.annotation.McpResource;
import org.springaicommunity.mcp.annotation.McpTool;
import org.springaicommunity.mcp.annotation.McpToolParam;
@@ -282,24 +287,95 @@ public CollectionService(SolrClient solrClient, ObjectMapper objectMapper) {
*
* @return JSON string containing the list of collections
*/
- @McpResource(uri = "solr://collections", name = "solr-collections", description = "List of all Solr collections available in the cluster", mimeType = "application/json")
+ @PreAuthorize("isAuthenticated()")
+ @McpResource(
+ uri = "solr://collections",
+ name = "solr-collections",
+ description = "List of all Solr collections available in the cluster",
+ mimeType = "application/json")
public String getCollectionsResource() throws SolrServerException, IOException {
return toJson(objectMapper, listCollections());
}
+ /** Maximum number of completion suggestions returned per request. */
+ static final int MAX_COMPLETION_RESULTS = 100;
+
/**
- * MCP Completion endpoint for collection name autocompletion.
+ * MCP Completion endpoint for the {@code {collection}} segment of the
+ * {@code solr://{collection}/schema} resource template.
*
*
- * Provides autocompletion support for the collection parameter in the schema
- * resource URI template. Returns all available collection names that MCP
- * clients can use to complete the {collection} placeholder.
- *
- * @return list of available collection names for autocompletion
+ * Returns collection names that start with the user-supplied prefix
+ * (case-insensitive). When the prefix is empty all collections are returned,
+ * subject to {@link #MAX_COMPLETION_RESULTS}. The results are sorted so that
+ * client UIs see a stable ordering.
+ *
+ * @param argument
+ * the partial value the client is completing; its
+ * {@link CompleteRequest.CompleteArgument#name() name} must be
+ * {@code collection}
+ * @return matching collection names, capped at {@link #MAX_COMPLETION_RESULTS}
*/
+ @PreAuthorize("isAuthenticated()")
@McpComplete(uri = "solr://{collection}/schema")
- public List completeCollectionForSchema() throws SolrServerException, IOException {
- return listCollections();
+ public List completeCollection(CompleteRequest.CompleteArgument argument) {
+ if (argument == null || !"collection".equals(argument.name())) {
+ return List.of();
+ }
+ String prefix = argument.value() == null ? "" : argument.value().toLowerCase(Locale.ROOT);
+ try {
+ return listCollections().stream().filter(c -> c != null && c.toLowerCase(Locale.ROOT).startsWith(prefix))
+ .sorted().limit(MAX_COMPLETION_RESULTS).toList();
+ } catch (SolrServerException | IOException _) {
+ return List.of();
+ }
+ }
+
+ /**
+ * Completion for the {@code collection} argument of the
+ * {@code search-collection} prompt (defined in {@code SearchService}).
+ *
+ *
+ * {@code @McpComplete} registers a handler per {@code (ref/prompt, name)} pair,
+ * so a prompt that takes a collection argument needs its own handler — the
+ * resource-template handler on {@link #completeCollection} only matches
+ * {@code ref/resource}. Each wrapper delegates so all collection-name
+ * completion shares one implementation and one cap.
+ */
+ @PreAuthorize("isAuthenticated()")
+ @McpComplete(prompt = PromptNames.SEARCH_COLLECTION)
+ public List completeSearchCollectionPromptArg(CompleteRequest.CompleteArgument argument) {
+ return completeCollection(argument);
+ }
+
+ /**
+ * Completion for the {@code collection} argument of the {@code index-data}
+ * prompt.
+ */
+ @PreAuthorize("isAuthenticated()")
+ @McpComplete(prompt = PromptNames.INDEX_DATA)
+ public List completeIndexDataPromptArg(CompleteRequest.CompleteArgument argument) {
+ return completeCollection(argument);
+ }
+
+ /**
+ * Completion for the {@code collection} argument of the {@code view-schema}
+ * prompt.
+ */
+ @PreAuthorize("isAuthenticated()")
+ @McpComplete(prompt = PromptNames.VIEW_SCHEMA)
+ public List completeViewSchemaPromptArg(CompleteRequest.CompleteArgument argument) {
+ return completeCollection(argument);
+ }
+
+ /**
+ * Completion for the {@code collection} argument of the {@code design-schema}
+ * prompt.
+ */
+ @PreAuthorize("isAuthenticated()")
+ @McpComplete(prompt = PromptNames.DESIGN_SCHEMA)
+ public List completeDesignSchemaPromptArg(CompleteRequest.CompleteArgument argument) {
+ return completeCollection(argument);
}
/**
@@ -331,7 +407,10 @@ public List completeCollectionForSchema() throws SolrServerException, IO
* @see CollectionAdminRequest.List
*/
@PreAuthorize("isAuthenticated()")
- @McpTool(name = "list-collections", annotations = @McpTool.McpAnnotations(readOnlyHint = true), description = "List solr collections")
+ @McpTool(
+ name = "list-collections",
+ annotations = @McpTool.McpAnnotations(readOnlyHint = true),
+ description = "List solr collections")
public List listCollections() throws SolrServerException, IOException {
CollectionAdminRequest.List request = new CollectionAdminRequest.List();
CollectionAdminResponse response = request.process(solrClient);
@@ -402,7 +481,10 @@ public List listCollections() throws SolrServerException, IOException {
* @see #extractCollectionName(String)
*/
@PreAuthorize("isAuthenticated()")
- @McpTool(name = "get-collection-stats", annotations = @McpTool.McpAnnotations(readOnlyHint = true), description = "Get stats/metrics on a Solr collection")
+ @McpTool(
+ name = "get-collection-stats",
+ annotations = @McpTool.McpAnnotations(readOnlyHint = true),
+ description = "Get stats/metrics on a Solr collection")
public SolrMetrics getCollectionStats(
@McpToolParam(description = "Solr collection to get stats/metrics for") String collection)
throws SolrServerException, IOException {
@@ -944,7 +1026,10 @@ private boolean validateCollectionExists(String collection) throws SolrServerExc
* @see SolrPingResponse
*/
@PreAuthorize("isAuthenticated()")
- @McpTool(name = "check-health", annotations = @McpTool.McpAnnotations(readOnlyHint = true), description = "Check health of a Solr collection")
+ @McpTool(
+ name = "check-health",
+ annotations = @McpTool.McpAnnotations(readOnlyHint = true),
+ description = "Check health of a Solr collection")
public SolrHealthStatus checkHealth(@McpToolParam(description = "Solr collection") String collection) {
String actualCollection = extractCollectionName(collection);
try {
@@ -996,13 +1081,20 @@ public SolrHealthStatus checkHealth(@McpToolParam(description = "Solr collection
* if there are I/O errors during communication
*/
@PreAuthorize("isAuthenticated()")
- @McpTool(name = "create-collection", annotations = @McpTool.McpAnnotations(destructiveHint = false), description = "Create a new Solr collection. "
- + "configSet defaults to _default, numShards and replicationFactor default to 1.")
+ @McpTool(
+ name = "create-collection",
+ annotations = @McpTool.McpAnnotations(destructiveHint = false),
+ description = "Create a new Solr collection. "
+ + "configSet defaults to _default, numShards and replicationFactor default to 1.")
public CollectionCreationResult createCollection(
@McpToolParam(description = "Name of the collection to create") String name,
@McpToolParam(description = "Configset name. Defaults to _default.", required = false) String configSet,
- @McpToolParam(description = "Number of shards (SolrCloud only). Defaults to 1.", required = false) Integer numShards,
- @McpToolParam(description = "Replication factor (SolrCloud only). Defaults to 1.", required = false) Integer replicationFactor)
+ @McpToolParam(
+ description = "Number of shards (SolrCloud only). Defaults to 1.",
+ required = false) Integer numShards,
+ @McpToolParam(
+ description = "Replication factor (SolrCloud only). Defaults to 1.",
+ required = false) Integer replicationFactor)
throws SolrServerException, IOException {
if (name == null || name.isBlank()) {
@@ -1018,4 +1110,78 @@ public CollectionCreationResult createCollection(
return new CollectionCreationResult(name, true, "Collection created successfully", new Date());
}
+
+ @PreAuthorize("isAuthenticated()")
+
+ @McpPrompt(
+ name = PromptNames.EXPLORE_COLLECTIONS,
+ title = "Explore Solr collections",
+ description = "Read-only walkthrough: list collections and characterise each by stats and health.")
+ public String exploreCollectionsPrompt() {
+ return """
+ You are exploring an Apache Solr cluster through MCP tools. Goal: produce a concise,
+ accurate picture of what already exists. This prompt is read-only; do not create or
+ modify anything.
+
+ 1. List collections.
+ - Call `list-collections` to get the full set of collection names.
+ - If the user mentioned a target collection by name, note whether it appears in the
+ list.
+
+ 2. Characterize each interesting collection.
+ - For each collection the user cares about (or a small representative sample if they
+ did not name one), call `get-collection-stats` to read numDocs, segment counts, and
+ cache/handler metrics, and `check-health` to confirm the collection responds to a
+ ping and the doc count is non-zero where expected.
+
+ 3. Summarize.
+ - Tell the user which collections exist, which look healthy, and which look empty or
+ stale.
+ - If the user's intent does not match any existing collection, suggest the
+ `setup-collection` prompt to create one.
+ """;
+ }
+
+ @PreAuthorize("isAuthenticated()")
+
+ @McpPrompt(
+ name = PromptNames.SETUP_COLLECTION,
+ title = "Set up a new Solr collection",
+ description = "Guided workflow: validate a name, pick configset / shards / replication factor, create the collection, and verify it.")
+ public String setupCollectionPrompt(@McpArg(
+ name = "name",
+ description = "Desired collection name. Lowercase letters, digits, underscores, hyphens — no spaces.",
+ required = true) String name,
+ @McpArg(
+ name = "purpose",
+ description = "Optional one-line description of what the collection is for (used only to ground the conversation).",
+ required = false) String purpose) {
+ String purposeLine = (purpose == null || purpose.isBlank()) ? "" : "\nPurpose: %s\n".formatted(purpose.strip());
+ return """
+ You are setting up a new Solr collection named `%s` through MCP tools.%s
+ 1. Validate the name.
+ - Lowercase letters, digits, underscores, hyphens only — no spaces or uppercase.
+ - Call `list-collections` and confirm `%s` does not already exist. If it does, stop
+ and tell the user.
+
+ 2. Choose creation parameters.
+ - Defaults: configset `%s`, %d shard(s), replicationFactor %d. These work for most
+ single-node and small SolrCloud setups.
+ - Only override if the user asked: a custom configset for a pre-built schema, more
+ shards for a large dataset, or higher replicationFactor for redundancy.
+
+ 3. Create.
+ - Call `create-collection` with `name=%s` and any non-default `configSet`,
+ `numShards`, `replicationFactor` values. Report the result (success flag +
+ message).
+
+ 4. Verify.
+ - Call `list-collections` again; `%s` should appear.
+ - Call `check-health` on `%s`; it should respond to ping.
+
+ Next step suggestion: define the schema. Use the `design-schema` prompt to design
+ fields for the dataset the user wants to index.
+ """.formatted(name, purposeLine, name, DEFAULT_CONFIGSET, DEFAULT_NUM_SHARDS,
+ DEFAULT_REPLICATION_FACTOR, name, name, name);
+ }
}
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 c09aae12..cae5ac32 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
@@ -24,6 +24,10 @@
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.mcp.server.indexing.documentcreator.IndexingDocumentCreator;
+import org.apache.solr.mcp.server.util.PromptNames;
+import org.apache.solr.mcp.server.util.PromptText;
+import org.springaicommunity.mcp.annotation.McpArg;
+import org.springaicommunity.mcp.annotation.McpPrompt;
import org.springaicommunity.mcp.annotation.McpTool;
import org.springaicommunity.mcp.annotation.McpToolParam;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -192,7 +196,10 @@ public IndexingService(SolrClient solrClient, IndexingDocumentCreator indexingDo
* @see #indexDocuments(String, List)
*/
@PreAuthorize("isAuthenticated()")
- @McpTool(name = "index-json-documents", annotations = @McpTool.McpAnnotations(idempotentHint = true), description = "Index documents from json String into Solr collection")
+ @McpTool(
+ name = "index-json-documents",
+ annotations = @McpTool.McpAnnotations(idempotentHint = true),
+ description = "Index documents from json String into Solr collection")
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 {
@@ -260,7 +267,10 @@ public String indexJsonDocuments(@McpToolParam(description = "Solr collection to
* @see #indexDocuments(String, List)
*/
@PreAuthorize("isAuthenticated()")
- @McpTool(name = "index-csv-documents", annotations = @McpTool.McpAnnotations(idempotentHint = true), description = "Index documents from CSV string into Solr collection")
+ @McpTool(
+ name = "index-csv-documents",
+ annotations = @McpTool.McpAnnotations(idempotentHint = true),
+ description = "Index documents from CSV string into Solr collection")
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 {
@@ -352,7 +362,10 @@ public String indexCsvDocuments(@McpToolParam(description = "Solr collection to
* @see #indexDocuments(String, List)
*/
@PreAuthorize("isAuthenticated()")
- @McpTool(name = "index-xml-documents", annotations = @McpTool.McpAnnotations(idempotentHint = true), description = "Index documents from XML string into Solr collection")
+ @McpTool(
+ name = "index-xml-documents",
+ annotations = @McpTool.McpAnnotations(idempotentHint = true),
+ description = "Index documents from XML string into Solr collection")
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 {
@@ -456,4 +469,76 @@ public int indexDocuments(String collection, List documents)
solrClient.commit(collection);
return successCount;
}
+
+ /**
+ * Maps an input-format keyword to the MCP tool and payload parameter for that
+ * format.
+ */
+ private record IndexTool(String name, String paramName) {
+ }
+
+ private static IndexTool resolveIndexTool(String format) {
+ String normalized = (format == null) ? "" : format.trim().toLowerCase();
+ return switch (normalized) {
+ case "json" -> new IndexTool("index-json-documents", "json");
+ case "csv" -> new IndexTool("index-csv-documents", "csv");
+ case "xml" -> new IndexTool("index-xml-documents", "xml");
+ default -> throw new IllegalArgumentException("format must be one of json/csv/xml, got: " + format);
+ };
+ }
+
+ @PreAuthorize("isAuthenticated()")
+
+ @McpPrompt(
+ name = PromptNames.INDEX_DATA,
+ title = "Index documents into a Solr collection",
+ description = "Guides the assistant through verifying the target schema, picking the right indexing tool for the input format, and confirming the result.")
+ public String indexDataPrompt(
+ @McpArg(
+ name = "collection",
+ description = "Target Solr collection name",
+ required = true) String collection,
+ @McpArg(
+ name = "format",
+ description = "Document format: 'json', 'csv', or 'xml'",
+ required = true) String format,
+ @McpArg(
+ name = "sample",
+ description = "Optional small sample of the input document(s) to ground field-shape decisions",
+ required = false) String sample) {
+ IndexTool indexTool = resolveIndexTool(format);
+ String sampleSection = PromptText.optionalCodeBlock(sample, "Sample input:",
+ "No sample was provided. If the user has not pasted the documents yet, ask for them (or a representative subset) before indexing.");
+ return """
+ You are indexing %s data into collection `%s` via MCP tools. Work incrementally and
+ verify after each step.
+
+ 1. Confirm the schema is ready.
+ - Call `get-schema` on `%s`. Confirm the fields the input references exist with
+ compatible types. If fields are missing or typed wrong, pause and run the
+ `design-schema` prompt to add them — indexing into a collection without the right
+ fields either fails or silently falls back to schemaless behavior, which can
+ pollute the configset.
+
+ 2. Inspect the input.
+ %s
+
+ 3. Index the documents.
+ - Call `%s` with `collection=%s` and `%s=`.
+ - The tool batches internally and commits at the end. The return value is the count
+ of successfully indexed documents.
+ - On error, read the message carefully: an "unknown field" error means the schema is
+ missing a field — go back to step 1 and run `design-schema`. A parse error means
+ the input format does not match the chosen tool — fix the payload and retry.
+
+ 4. Verify the count.
+ - Call `check-health` on `%s` and confirm the reported doc count increased by the
+ expected amount, OR call `search` with `query=*:*` and `rows=0` and read
+ `numFound`.
+
+ Next step suggestion: once data is indexed, the `search-collection` prompt drives
+ searching it.
+ """.formatted(indexTool.paramName(), collection, collection, sampleSection, indexTool.name(),
+ collection, indexTool.paramName(), collection);
+ }
}
diff --git a/src/main/java/org/apache/solr/mcp/server/schema/SchemaService.java b/src/main/java/org/apache/solr/mcp/server/schema/SchemaService.java
index 388269d6..8e0549a1 100644
--- a/src/main/java/org/apache/solr/mcp/server/schema/SchemaService.java
+++ b/src/main/java/org/apache/solr/mcp/server/schema/SchemaService.java
@@ -17,6 +17,7 @@
package org.apache.solr.mcp.server.schema;
import static org.apache.solr.mcp.server.util.JsonUtils.toJson;
+import static org.apache.solr.mcp.server.util.PromptText.optionalCodeBlock;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.micrometer.observation.annotation.Observed;
@@ -31,6 +32,9 @@
import org.apache.solr.client.solrj.request.schema.FieldTypeDefinition;
import org.apache.solr.client.solrj.request.schema.SchemaRequest;
import org.apache.solr.client.solrj.response.schema.SchemaRepresentation;
+import org.apache.solr.mcp.server.util.PromptNames;
+import org.springaicommunity.mcp.annotation.McpArg;
+import org.springaicommunity.mcp.annotation.McpPrompt;
import org.springaicommunity.mcp.annotation.McpResource;
import org.springaicommunity.mcp.annotation.McpTool;
import org.springaicommunity.mcp.annotation.McpToolParam;
@@ -171,7 +175,12 @@ public SchemaService(SolrClient solrClient, ObjectMapper objectMapper) {
* the name of the collection to retrieve schema for
* @return JSON string containing the schema representation
*/
- @McpResource(uri = "solr://{collection}/schema", name = "solr-collection-schema", description = "Schema definition for a Solr collection including fields, field types, and copy fields", mimeType = "application/json")
+ @PreAuthorize("isAuthenticated()")
+ @McpResource(
+ uri = "solr://{collection}/schema",
+ name = "solr-collection-schema",
+ description = "Schema definition for a Solr collection including fields, field types, and copy fields",
+ mimeType = "application/json")
public String getSchemaResource(String collection) {
try {
return toJson(objectMapper, getSchema(collection));
@@ -260,25 +269,32 @@ public String getSchemaResource(String collection) {
* @see org.apache.solr.client.solrj.response.schema.SchemaResponse
*/
@PreAuthorize("isAuthenticated()")
- @McpTool(name = "get-schema", annotations = @McpTool.McpAnnotations(readOnlyHint = true), description = "Get schema for a Solr collection")
+ @McpTool(
+ name = "get-schema",
+ annotations = @McpTool.McpAnnotations(readOnlyHint = true),
+ description = "Get schema for a Solr collection")
public SchemaRepresentation getSchema(String collection) throws Exception {
SchemaRequest schemaRequest = new SchemaRequest();
return schemaRequest.process(solrClient, collection).getSchemaRepresentation();
}
@PreAuthorize("isAuthenticated()")
- @McpTool(name = "add-fields", annotations = @McpTool.McpAnnotations(destructiveHint = false), description = "Add one or more fields to a Solr collection schema. "
- + "Call get-schema first to inspect existing field configuration before adding. "
- + "Each field map follows the Solr Schema API add-field shape: required keys "
- + "'name' and 'type', plus optional 'stored', 'indexed', 'docValues', "
- + "'multiValued', 'required', 'omitNorms', etc. "
- + "Example: {\"name\":\"platform\",\"type\":\"string\",\"stored\":true,\"indexed\":true,\"docValues\":true}. "
- + "Use 'strings' (not 'string') for multi-valued string fields. "
- + "Note: this only adds new fields; existing fields cannot be modified. "
- + "Solr's Schema API is transactional — if any command in the batch fails, "
- + "none are applied. On failure, fix the invalid field(s) and retry the whole batch.")
+ @McpTool(
+ name = "add-fields",
+ annotations = @McpTool.McpAnnotations(destructiveHint = false),
+ description = "Add one or more fields to a Solr collection schema. "
+ + "Call get-schema first to inspect existing field configuration before adding. "
+ + "Each field map follows the Solr Schema API add-field shape: required keys "
+ + "'name' and 'type', plus optional 'stored', 'indexed', 'docValues', "
+ + "'multiValued', 'required', 'omitNorms', etc. "
+ + "Example: {\"name\":\"platform\",\"type\":\"string\",\"stored\":true,\"indexed\":true,\"docValues\":true}. "
+ + "Use 'strings' (not 'string') for multi-valued string fields. "
+ + "Note: this only adds new fields; existing fields cannot be modified. "
+ + "Solr's Schema API is transactional — if any command in the batch fails, "
+ + "none are applied. On failure, fix the invalid field(s) and retry the whole batch.")
public SchemaUpdateResult addFields(@McpToolParam(description = "Solr collection name") String collection,
- @McpToolParam(description = "List of field definitions (Solr add-field JSON shape)") List