This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-mcp-server.git

commit 5feb4210127cc09028eee0da3f3d630a67fb0db4
Author: Robert Munteanu <[email protected]>
AuthorDate: Wed Dec 10 11:21:44 2025 +0100

    chore(mcp): minor code cleanups in the BundleResourceContribution
---
 .../impl/contribs/BundleResourceContribution.java  | 74 +++++++++++++++-------
 .../mcp/server/impl/contribs/BundleState.java      | 57 +++++++++++++++++
 2 files changed, 108 insertions(+), 23 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/BundleResourceContribution.java
 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/BundleResourceContribution.java
index 605eeb3..fc7ac2a 100644
--- 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/BundleResourceContribution.java
+++ 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/BundleResourceContribution.java
@@ -26,6 +26,7 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import io.modelcontextprotocol.server.McpStatelessServerFeatures;
+import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncCompletionSpecification;
 import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncResourceSpecification;
 import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncResourceTemplateSpecification;
 import io.modelcontextprotocol.spec.McpSchema;
@@ -42,17 +43,9 @@ import org.osgi.service.component.annotations.Component;
 @Component
 public class BundleResourceContribution implements McpServerContribution {
 
-    private static String getStateString(int state) {
-        return switch (state) {
-            case Bundle.UNINSTALLED -> "UNINSTALLED";
-            case Bundle.INSTALLED -> "INSTALLED";
-            case Bundle.RESOLVED -> "RESOLVED";
-            case Bundle.STARTING -> "STARTING";
-            case Bundle.STOPPING -> "STOPPING";
-            case Bundle.ACTIVE -> "ACTIVE";
-            default -> "UNKNOWN";
-        };
-    }
+    private static final String RESOURCE_TEMPLATE_BUNDLES_STATE_PREFIX = 
"bundles://state/";
+    private static final String RESOURCE_TEMPLATE_BUNDLES_STATE_PATTERN =
+            RESOURCE_TEMPLATE_BUNDLES_STATE_PREFIX + "{state}";
 
     private BundleContext ctx;
 
@@ -72,10 +65,8 @@ public class BundleResourceContribution implements 
McpServerContribution {
                         .mimeType("text/plain")
                         .build(),
                 (context, request) -> {
-                    String bundleInfo = Stream.of(ctx.getBundles())
-                            .map(b -> "Bundle " + b.getSymbolicName() + " is 
in state " + getStateString(b.getState())
-                                    + " (" + b.getState() + ")")
-                            .collect(Collectors.joining("\n"));
+                    String bundleInfo =
+                            
Stream.of(ctx.getBundles()).map(this::describe).collect(Collectors.joining("\n"));
 
                     TextResourceContents contents = new 
TextResourceContents("bundle://", "text/plain", bundleInfo);
 
@@ -87,22 +78,59 @@ public class BundleResourceContribution implements 
McpServerContribution {
     public Optional<SyncResourceTemplateSpecification> 
getSyncResourceTemplateSpecification() {
         return Optional.of(new 
McpStatelessServerFeatures.SyncResourceTemplateSpecification(
                 new ResourceTemplate.Builder()
-                        .uriTemplate("bundles://state/{state}")
+                        .uriTemplate(RESOURCE_TEMPLATE_BUNDLES_STATE_PATTERN)
                         .name("bundles")
                         .build(),
                 (context, request) -> {
-                    String bundleInfo = "";
-                    if 
("bundles://state/resolved".equals(request.uri().toLowerCase(Locale.ENGLISH))) {
+                    String requestedState = 
request.uri().substring(RESOURCE_TEMPLATE_BUNDLES_STATE_PREFIX.length());
+                    try {
+                        BundleState bundleState = 
BundleState.valueOf(requestedState.toUpperCase(Locale.ENGLISH));
+                        if (!bundleState.isValid()) {
+                            throw new IllegalArgumentException("Invalid bundle 
state: " + requestedState);
+                        }
+                        String bundleInfo = "";
+                        // extract desired state from URI
                         bundleInfo = Arrays.stream(ctx.getBundles())
-                                .filter(b -> b.getState() == Bundle.RESOLVED)
-                                .map(b -> "Bundle " + b.getSymbolicName() + " 
is in state "
-                                        + getStateString(b.getState()) + " (" 
+ b.getState() + ")")
+                                .filter(b -> b.getState() == 
bundleState.getState())
+                                .map(this::describe)
                                 .collect(Collectors.joining("\n"));
+
+                        TextResourceContents contents =
+                                new TextResourceContents(request.uri(), 
"text/plain", bundleInfo);
+
+                        return new ReadResourceResult(List.of(contents));
+                    } catch (IllegalArgumentException e) {
+                        return new ReadResourceResult(List.of(new 
TextResourceContents(
+                                request.uri(), "text/plain", "Invalid bundle 
state requested: " + requestedState)));
                     }
+                }));
+    }
 
-                    TextResourceContents contents = new 
TextResourceContents(request.uri(), "text/plain", bundleInfo);
+    @Override
+    public Optional<SyncCompletionSpecification> 
getSyncCompletionSpecification() {
 
-                    return new ReadResourceResult(List.of(contents));
+        return Optional.of(new 
McpStatelessServerFeatures.SyncCompletionSpecification(
+                new McpSchema.ResourceReference("ref/resource", 
RESOURCE_TEMPLATE_BUNDLES_STATE_PATTERN),
+                (context, request) -> {
+
+                    // expect argument name to always be "state"
+                    String requestedState = request.argument().value();
+                    List<String> states = Stream.of(BundleState.values())
+                            .filter(BundleState::isValid)
+                            .map(s -> s.name().toLowerCase(Locale.ENGLISH))
+                            .toList();
+                    if (requestedState != null && !requestedState.isEmpty()) {
+                        states = states.stream()
+                                .filter(s -> 
s.startsWith(requestedState.toLowerCase(Locale.ENGLISH)))
+                                .toList();
+                    }
+                    return new McpSchema.CompleteResult(
+                            new 
McpSchema.CompleteResult.CompleteCompletion(states, states.size(), false));
                 }));
     }
+
+    private String describe(Bundle b) {
+        return "Bundle " + b.getSymbolicName() + " (version " + b.getVersion() 
+ ") is in state "
+                + BundleState.fromState(b.getState()) + " (" + b.getState() + 
")";
+    }
 }
diff --git 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/BundleState.java 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/BundleState.java
new file mode 100644
index 0000000..29e5bba
--- /dev/null
+++ b/src/main/java/org/apache/sling/mcp/server/impl/contribs/BundleState.java
@@ -0,0 +1,57 @@
+/*
+ * 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.sling.mcp.server.impl.contribs;
+
+import org.osgi.framework.Bundle;
+
+/**
+ * Enum representing OSGi bundle states.
+ */
+public enum BundleState {
+    UNINSTALLED(Bundle.UNINSTALLED),
+    INSTALLED(Bundle.INSTALLED),
+    RESOLVED(Bundle.RESOLVED),
+    STARTING(Bundle.STARTING),
+    STOPPING(Bundle.STOPPING),
+    ACTIVE(Bundle.ACTIVE),
+    UNKNOWN(-1);
+
+    private final int state;
+
+    BundleState(int state) {
+        this.state = state;
+    }
+
+    public static BundleState fromState(int state) {
+        for (BundleState bs : values()) {
+            if (bs.state == state) {
+                return bs;
+            }
+        }
+        return UNKNOWN;
+    }
+
+    public boolean isValid() {
+        return this != UNKNOWN;
+    }
+
+    public int getState() {
+        return state;
+    }
+}

Reply via email to