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 <[email protected]>
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 <[email protected]>
    
    * 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 <[email protected]>
    
    * chore: remove useless comments
    
    ---------
    
    Co-authored-by: Claude Opus 4.5 <[email protected]>
---
 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);
                });
        }
 }

Reply via email to