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; + } +}
