You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ep...@apache.org on 2026/01/07 10:38:51 UTC

(solr-mcp) branch main updated: feat: add MCP resource and completion for listing Solr collections and their schema (#31)

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 f7c02d2  feat: add MCP resource and completion for listing Solr collections and their schema (#31)
f7c02d2 is described below

commit f7c02d2cb466a69d1b5c93fce0224818aa4ebf8d
Author: Aditya Parikh <ad...@gmail.com>
AuthorDate: Wed Jan 7 06:38:46 2026 -0400

    feat: add MCP resource and completion for listing Solr collections and their schema (#31)
    
    * feat: add MCP Resources for collections and schema
    
    Add @McpResource and @McpComplete annotations from spring-ai-mcp-annotations
    library to expose Solr metadata as MCP Resources:
    
    - solr://collections: Lists all available Solr collections
    - solr://{collection}/schema: Returns schema definition for a collection
    
    The schema resource supports autocompletion for the {collection} parameter
    using @McpComplete, allowing MCP clients to discover available collections.
    
    Also adds JsonUtils utility class for consistent JSON serialization in
    MCP Resource responses.
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    Co-Authored-By: Claude Opus 4.5 <no...@anthropic.com>
    
    * docs: add MCP Inspector screenshots for resources
    
    Add screenshots showing:
    - MCP Inspector listing available resources
    - Resource autocompletion for collection names
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    Co-Authored-By: Claude Opus 4.5 <no...@anthropic.com>
    
    * chore: remove useless comments
    
    ---------
    
    Co-authored-by: Claude Opus 4.5 <no...@anthropic.com>
---
 README.md                                          |  17 ++++++
 images/mcp-inspector-list-resources.png            | Bin 0 -> 140711 bytes
 images/mcp-inspector-resource-completion.png       | Bin 0 -> 111983 bytes
 .../mcp/server/metadata/CollectionService.java     |  62 ++++++++++++++++++---
 .../solr/mcp/server/metadata/SchemaService.java    |  36 +++++++++++-
 .../org/apache/solr/mcp/server/util/JsonUtils.java |  58 +++++++++++++++++++
 .../mcp/server/metadata/CollectionServiceTest.java |  17 +++---
 .../mcp/server/metadata/SchemaServiceTest.java     |  12 ++--
 8 files changed, 181 insertions(+), 21 deletions(-)

diff --git a/README.md b/README.md
index 011f94a..e50085b 100644
--- a/README.md
+++ b/README.md
@@ -235,6 +235,23 @@ For complete setup instructions, see [docs/AUTH0_SETUP.md](docs/AUTH0_SETUP.md)
 | `checkHealth` | Check the health status of a collection |
 | `getSchema` | Retrieve schema information for a collection |
 
+## Available MCP Resources
+
+MCP Resources provide a way to expose data that can be read by MCP clients. The Solr MCP Server provides the following resources:
+
+| Resource URI | Description |
+|--------------|-------------|
+| `solr://collections` | List of all Solr collections available in the cluster |
+| `solr://{collection}/schema` | Schema definition for a specific collection (supports autocompletion) |
+
+### Resource Autocompletion
+
+The `solr://{collection}/schema` resource supports autocompletion for the `{collection}` parameter. MCP clients can use the completion API to get a list of available collection names.
+
+![MCP Inspector Resources](images/mcp-inspector-list-resources.png)
+
+![MCP Inspector Resource Completion](images/mcp-inspector-resource-completion.png)
+
 ## Screenshots
 
 - Claude Desktop (STDIO):
diff --git a/images/mcp-inspector-list-resources.png b/images/mcp-inspector-list-resources.png
new file mode 100644
index 0000000..0507164
Binary files /dev/null and b/images/mcp-inspector-list-resources.png differ
diff --git a/images/mcp-inspector-resource-completion.png b/images/mcp-inspector-resource-completion.png
new file mode 100644
index 0000000..6ac2d54
Binary files /dev/null and b/images/mcp-inspector-resource-completion.png differ
diff --git a/src/main/java/org/apache/solr/mcp/server/metadata/CollectionService.java b/src/main/java/org/apache/solr/mcp/server/metadata/CollectionService.java
index 165b10b..34a9a59 100644
--- a/src/main/java/org/apache/solr/mcp/server/metadata/CollectionService.java
+++ b/src/main/java/org/apache/solr/mcp/server/metadata/CollectionService.java
@@ -16,12 +16,7 @@
  */
 package org.apache.solr.mcp.server.metadata;
 
-import static org.apache.solr.mcp.server.metadata.CollectionUtils.*;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.client.solrj.SolrRequest;
@@ -31,15 +26,31 @@ import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.CoreAdminRequest;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.client.solrj.request.LukeRequest;
-import org.apache.solr.client.solrj.response.*;
+import org.apache.solr.client.solrj.response.CollectionAdminResponse;
+import org.apache.solr.client.solrj.response.CoreAdminResponse;
+import org.apache.solr.client.solrj.response.LukeResponse;
+import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.client.solrj.response.SolrPingResponse;
 import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.mcp.server.config.SolrConfigurationProperties;
+import org.springaicommunity.mcp.annotation.McpComplete;
+import org.springaicommunity.mcp.annotation.McpResource;
 import org.springaicommunity.mcp.annotation.McpTool;
 import org.springaicommunity.mcp.annotation.McpToolParam;
 import org.springframework.stereotype.Service;
 
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import static org.apache.solr.mcp.server.metadata.CollectionUtils.getFloat;
+import static org.apache.solr.mcp.server.metadata.CollectionUtils.getInteger;
+import static org.apache.solr.mcp.server.metadata.CollectionUtils.getLong;
+import static org.apache.solr.mcp.server.util.JsonUtils.toJson;
+
 /**
  * Spring Service providing comprehensive Solr collection management and
  * monitoring capabilities for Model Context Protocol (MCP) clients.
@@ -242,6 +253,8 @@ public class CollectionService {
 	/** SolrJ client for communicating with Solr server */
 	private final SolrClient solrClient;
 
+	private final ObjectMapper objectMapper;
+
 	/**
 	 * Constructs a new CollectionService with the required dependencies.
 	 *
@@ -251,11 +264,44 @@ public class CollectionService {
 	 *
 	 * @param solrClient
 	 *            the SolrJ client instance for communicating with Solr
+	 * @param objectMapper
+	 *            the Jackson ObjectMapper for JSON serialization
 	 * @see SolrClient
 	 * @see SolrConfigurationProperties
 	 */
-	public CollectionService(SolrClient solrClient) {
+	public CollectionService(SolrClient solrClient, ObjectMapper objectMapper) {
 		this.solrClient = solrClient;
+		this.objectMapper = objectMapper;
+	}
+
+	/**
+	 * MCP Resource endpoint that returns a list of all available Solr collections.
+	 *
+	 * <p>
+	 * This resource provides a simple way for MCP clients to discover what
+	 * collections are available in the Solr cluster. The returned JSON contains an
+	 * array of collection names.
+	 *
+	 * @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")
+	public String getCollectionsResource() {
+		return toJson(objectMapper, listCollections());
+	}
+
+	/**
+	 * MCP Completion endpoint for collection name autocompletion.
+	 *
+	 * <p>
+	 * 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
+	 */
+	@McpComplete(uri = "solr://{collection}/schema")
+	public List<String> completeCollectionForSchema() {
+		return listCollections();
 	}
 
 	/**
diff --git a/src/main/java/org/apache/solr/mcp/server/metadata/SchemaService.java b/src/main/java/org/apache/solr/mcp/server/metadata/SchemaService.java
index ea131f4..68e30a6 100644
--- a/src/main/java/org/apache/solr/mcp/server/metadata/SchemaService.java
+++ b/src/main/java/org/apache/solr/mcp/server/metadata/SchemaService.java
@@ -16,12 +16,16 @@
  */
 package org.apache.solr.mcp.server.metadata;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.request.schema.SchemaRequest;
 import org.apache.solr.client.solrj.response.schema.SchemaRepresentation;
+import org.springaicommunity.mcp.annotation.McpResource;
 import org.springaicommunity.mcp.annotation.McpTool;
 import org.springframework.stereotype.Service;
 
+import static org.apache.solr.mcp.server.util.JsonUtils.toJson;
+
 /**
  * Spring Service providing schema introspection and management capabilities for
  * Apache Solr collections.
@@ -122,8 +126,10 @@ public class SchemaService {
 	/** SolrJ client for communicating with Solr server */
 	private final SolrClient solrClient;
 
+	private final ObjectMapper objectMapper;
+
 	/**
-	 * Constructs a new SchemaService with the required SolrClient dependency.
+	 * Constructs a new SchemaService with the required dependencies.
 	 *
 	 * <p>
 	 * This constructor is automatically called by Spring's dependency injection
@@ -132,10 +138,36 @@ public class SchemaService {
 	 *
 	 * @param solrClient
 	 *            the SolrJ client instance for communicating with Solr
+	 * @param objectMapper
+	 *            the Jackson ObjectMapper for JSON serialization
 	 * @see SolrClient
 	 */
-	public SchemaService(SolrClient solrClient) {
+	public SchemaService(SolrClient solrClient, ObjectMapper objectMapper) {
 		this.solrClient = solrClient;
+		this.objectMapper = objectMapper;
+	}
+
+	/**
+	 * MCP Resource endpoint that returns the schema for a specified Solr
+	 * collection.
+	 *
+	 * <p>
+	 * This resource uses a URI template with {collection} placeholder that can be
+	 * completed using the {@code @McpComplete} endpoint in CollectionService. The
+	 * returned JSON contains the complete schema definition including fields, field
+	 * types, dynamic fields, and copy fields.
+	 *
+	 * @param collection
+	 *            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")
+	public String getSchemaResource(String collection) {
+		try {
+			return toJson(objectMapper, getSchema(collection));
+		} catch (Exception e) {
+			return "{\"error\": \"" + e.getMessage() + "\"}";
+		}
 	}
 
 	/**
diff --git a/src/main/java/org/apache/solr/mcp/server/util/JsonUtils.java b/src/main/java/org/apache/solr/mcp/server/util/JsonUtils.java
new file mode 100644
index 0000000..6ecc3bc
--- /dev/null
+++ b/src/main/java/org/apache/solr/mcp/server/util/JsonUtils.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.mcp.server.util;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Utility class for JSON serialization operations.
+ *
+ * <p>
+ * Provides common JSON conversion methods used throughout the MCP server,
+ * particularly for MCP Resource responses that need to return JSON strings.
+ *
+ * @version 0.0.1
+ * @since 0.0.1
+ */
+public final class JsonUtils {
+
+	private JsonUtils() {
+		// Utility class - prevent instantiation
+	}
+
+	/**
+	 * Converts an object to its JSON string representation.
+	 *
+	 * <p>
+	 * Used by MCP Resource methods that need to return serialized JSON responses.
+	 * On serialization failure, returns an error JSON object.
+	 *
+	 * @param objectMapper
+	 *            the Jackson ObjectMapper for serialization
+	 * @param obj
+	 *            the object to serialize
+	 * @return JSON string representation, or error JSON on failure
+	 */
+	public static String toJson(ObjectMapper objectMapper, Object obj) {
+		try {
+			return objectMapper.writeValueAsString(obj);
+		} catch (JsonProcessingException e) {
+			return "{\"error\": \"Failed to serialize response\"}";
+		}
+	}
+}
diff --git a/src/test/java/org/apache/solr/mcp/server/metadata/CollectionServiceTest.java b/src/test/java/org/apache/solr/mcp/server/metadata/CollectionServiceTest.java
index a5afc42..19888ae 100644
--- a/src/test/java/org/apache/solr/mcp/server/metadata/CollectionServiceTest.java
+++ b/src/test/java/org/apache/solr/mcp/server/metadata/CollectionServiceTest.java
@@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.*;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import java.io.IOException;
 import java.lang.reflect.Method;
 import java.util.Arrays;
@@ -61,9 +62,11 @@ class CollectionServiceTest {
 
 	private CollectionService collectionService;
 
+	private final ObjectMapper objectMapper = new ObjectMapper();
+
 	@BeforeEach
 	void setUp() {
-		collectionService = new CollectionService(solrClient);
+		collectionService = new CollectionService(solrClient, objectMapper);
 	}
 
 	// Constructor tests
@@ -76,7 +79,7 @@ class CollectionServiceTest {
 	void listCollections_WithCloudSolrClient_ShouldReturnCollections() throws Exception {
 		// Given - This test verifies the service can be constructed with
 		// CloudSolrClient
-		CollectionService cloudService = new CollectionService(cloudSolrClient);
+		CollectionService cloudService = new CollectionService(cloudSolrClient, objectMapper);
 
 		// Note: This test cannot fully exercise listCollections() because it requires
 		// mocking static methods in CollectionAdminRequest which requires PowerMock or
@@ -702,7 +705,7 @@ class CollectionServiceTest {
 
 		when(cloudClient.request(any(), any())).thenReturn(response);
 
-		CollectionService service = new CollectionService(cloudClient);
+		CollectionService service = new CollectionService(cloudClient, objectMapper);
 		List<String> result = service.listCollections();
 
 		assertNotNull(result);
@@ -720,7 +723,7 @@ class CollectionServiceTest {
 
 		when(cloudClient.request(any(), any())).thenReturn(response);
 
-		CollectionService service = new CollectionService(cloudClient);
+		CollectionService service = new CollectionService(cloudClient, objectMapper);
 		List<String> result = service.listCollections();
 
 		assertNotNull(result);
@@ -732,7 +735,7 @@ class CollectionServiceTest {
 		CloudSolrClient cloudClient = mock(CloudSolrClient.class);
 		when(cloudClient.request(any(), any())).thenThrow(new SolrServerException("Connection error"));
 
-		CollectionService service = new CollectionService(cloudClient);
+		CollectionService service = new CollectionService(cloudClient, objectMapper);
 		List<String> result = service.listCollections();
 
 		assertNotNull(result);
@@ -755,7 +758,7 @@ class CollectionServiceTest {
 		// Mock the solrClient request to return the response
 		when(solrClient.request(any(), any())).thenReturn(response);
 
-		CollectionService service = new CollectionService(solrClient);
+		CollectionService service = new CollectionService(solrClient, objectMapper);
 		List<String> result = service.listCollections();
 
 		assertNotNull(result);
@@ -768,7 +771,7 @@ class CollectionServiceTest {
 	void listCollections_NonCloudClient_Error() throws Exception {
 		when(solrClient.request(any(), any())).thenThrow(new IOException("IO error"));
 
-		CollectionService service = new CollectionService(solrClient);
+		CollectionService service = new CollectionService(solrClient, objectMapper);
 		List<String> result = service.listCollections();
 
 		assertNotNull(result);
diff --git a/src/test/java/org/apache/solr/mcp/server/metadata/SchemaServiceTest.java b/src/test/java/org/apache/solr/mcp/server/metadata/SchemaServiceTest.java
index 56321b8..964c3a5 100644
--- a/src/test/java/org/apache/solr/mcp/server/metadata/SchemaServiceTest.java
+++ b/src/test/java/org/apache/solr/mcp/server/metadata/SchemaServiceTest.java
@@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import java.io.IOException;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
@@ -43,6 +44,9 @@ class SchemaServiceTest {
 	@Mock
 	private SolrClient solrClient;
 
+	@Mock
+	private ObjectMapper objectMapper;
+
 	@Mock
 	private SchemaResponse schemaResponse;
 
@@ -53,13 +57,13 @@ class SchemaServiceTest {
 
 	@BeforeEach
 	void setUp() {
-		schemaService = new SchemaService(solrClient);
+		schemaService = new SchemaService(solrClient, objectMapper);
 	}
 
 	@Test
 	void testSchemaService_InstantiatesCorrectly() {
 		// Given/When
-		SchemaService service = new SchemaService(solrClient);
+		SchemaService service = new SchemaService(solrClient, objectMapper);
 
 		// Then
 		assertNotNull(service, "SchemaService should be instantiated correctly");
@@ -132,7 +136,7 @@ class SchemaServiceTest {
 	@Test
 	void testConstructor() {
 		// Test that constructor properly initializes the service
-		SchemaService service = new SchemaService(solrClient);
+		SchemaService service = new SchemaService(solrClient, objectMapper);
 		assertNotNull(service);
 	}
 
@@ -140,7 +144,7 @@ class SchemaServiceTest {
 	void testConstructor_WithNullClient() {
 		// Test constructor with null client
 		assertDoesNotThrow(() -> {
-			new SchemaService(null);
+			new SchemaService(null, objectMapper);
 		});
 	}
 }