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-whiteboard.git
The following commit(s) were added to refs/heads/master by this push:
new cc713a1c chore(mcp): minor code cleanups in the
BundleResourceContribution
cc713a1c is described below
commit cc713a1c72f34fc27a709b6f60e53142a33442a1
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/mcp-server/src/main/java/org/apache/sling/mcp/server/impl/contribs/BundleResourceContribution.java
b/mcp-server/src/main/java/org/apache/sling/mcp/server/impl/contribs/BundleResourceContribution.java
index 605eeb3f..fc7ac2ab 100644
---
a/mcp-server/src/main/java/org/apache/sling/mcp/server/impl/contribs/BundleResourceContribution.java
+++
b/mcp-server/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/mcp-server/src/main/java/org/apache/sling/mcp/server/impl/contribs/BundleState.java
b/mcp-server/src/main/java/org/apache/sling/mcp/server/impl/contribs/BundleState.java
new file mode 100644
index 00000000..29e5bba8
--- /dev/null
+++
b/mcp-server/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;
+ }
+}