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 f381d1066e0fa668e6c888560f61c882478d1220
Author: Robert Munteanu <[email protected]>
AuthorDate: Tue Jan 13 15:08:06 2026 +0100

    chore(mcp-server): move contributions to a separate bundle
---
 README.md                                          |  10 +-
 .../impl/contribs/BundleResourceContribution.java  | 142 ------
 .../mcp/server/impl/contribs/BundleState.java      |  57 ---
 .../contribs/ComponentResourceContribution.java    | 126 -----
 .../server/impl/contribs/LogToolContribution.java  | 359 --------------
 .../contribs/OsgiBundleDiagnosticContribution.java | 525 ---------------------
 .../contribs/OsgiDiagnosticPromptContribution.java | 155 ------
 .../impl/contribs/RecentRequestsContribution.java  |  73 ---
 .../impl/contribs/RefreshPackagesContribution.java |  74 ---
 .../impl/contribs/ServletPromptContribution.java   |  71 ---
 .../apache/sling/mcp/server/spi/package-info.java  |  18 +
 11 files changed, 26 insertions(+), 1584 deletions(-)

diff --git a/README.md b/README.md
index 599c6a0..d2988d4 100644
--- a/README.md
+++ b/README.md
@@ -4,10 +4,16 @@ Experimental MCP Server implementation for Apache Sling.
 
 ## Usage
 
-Start up the MCP server, based on the Apache Sling Starter
+Build the project with Maven and start up the MCP server, based on the Apache 
Sling Starter:
 
 ```
-$ mvn package feature-launcher:start feature-launcher:stop 
-Dfeature-launcher.waitForInput
+$ mvn install feature-launcher:start feature-launcher:stop 
-Dfeature-launcher.waitForInput
+```
+
+Then build and deploy the sibling contributions package:
+
+```
+$ mvn -f ../mcp-server-contributions/ install sling:install 
 ```
 
 Then open up your coding assistant tool and add an remote MCP server with 
location http://localhost:8080/mcp .
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
deleted file mode 100644
index d54e598..0000000
--- 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/BundleResourceContribution.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * 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 java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-import java.util.Optional;
-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;
-import io.modelcontextprotocol.spec.McpSchema.ReadResourceResult;
-import io.modelcontextprotocol.spec.McpSchema.Resource;
-import io.modelcontextprotocol.spec.McpSchema.ResourceTemplate;
-import io.modelcontextprotocol.spec.McpSchema.TextResourceContents;
-import org.apache.sling.mcp.server.spi.McpServerContribution;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.wiring.BundleRevision;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-
-@Component
-public class BundleResourceContribution implements McpServerContribution {
-
-    private static final String URI_BUNDLES_ALL = "bundles://all";
-    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;
-
-    @Activate
-    public BundleResourceContribution(BundleContext ctx) {
-        this.ctx = ctx;
-    }
-
-    @Override
-    public Optional<SyncResourceSpecification> getSyncResourceSpecification() {
-
-        return Optional.of(new 
McpStatelessServerFeatures.SyncResourceSpecification(
-                new Resource.Builder()
-                        .name("bundles")
-                        .uri(URI_BUNDLES_ALL)
-                        .description(
-                                "List all OSGi bundles with symbolic name, 
version, and state. Fragment bundles are marked with [Fragment]")
-                        .mimeType("text/plain")
-                        .build(),
-                (context, request) -> {
-                    String bundleInfo =
-                            
Stream.of(ctx.getBundles()).map(this::describe).collect(Collectors.joining("\n"));
-
-                    TextResourceContents contents = new 
TextResourceContents(URI_BUNDLES_ALL, "text/plain", bundleInfo);
-
-                    return new McpSchema.ReadResourceResult(List.of(contents));
-                }));
-    }
-
-    @Override
-    public Optional<SyncResourceTemplateSpecification> 
getSyncResourceTemplateSpecification() {
-        return Optional.of(new 
McpStatelessServerFeatures.SyncResourceTemplateSpecification(
-                new ResourceTemplate.Builder()
-                        .uriTemplate(RESOURCE_TEMPLATE_BUNDLES_STATE_PATTERN)
-                        .name("bundles")
-                        .build(),
-                (context, request) -> {
-                    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 = Arrays.stream(ctx.getBundles())
-                                .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)));
-                    }
-                }));
-    }
-
-    @Override
-    public Optional<SyncCompletionSpecification> 
getSyncCompletionSpecification() {
-
-        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) {
-        boolean isFragment = 
Optional.ofNullable(b.adapt(BundleRevision.class)).stream()
-                .map(br -> (br.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0)
-                .findAny()
-                .orElse(false);
-        String additionalInfo = isFragment ? " [Fragment]" : "";
-        return "Bundle " + b.getSymbolicName() + additionalInfo + " (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
deleted file mode 100644
index 29e5bba..0000000
--- a/src/main/java/org/apache/sling/mcp/server/impl/contribs/BundleState.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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;
-    }
-}
diff --git 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/ComponentResourceContribution.java
 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/ComponentResourceContribution.java
deleted file mode 100644
index 937ad9d..0000000
--- 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/ComponentResourceContribution.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * 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 java.util.Collection;
-import java.util.List;
-import java.util.Locale;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-import io.modelcontextprotocol.server.McpStatelessServerFeatures;
-import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncResourceSpecification;
-import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncResourceTemplateSpecification;
-import io.modelcontextprotocol.spec.McpSchema;
-import io.modelcontextprotocol.spec.McpSchema.ReadResourceResult;
-import io.modelcontextprotocol.spec.McpSchema.Resource;
-import io.modelcontextprotocol.spec.McpSchema.ResourceTemplate;
-import io.modelcontextprotocol.spec.McpSchema.TextResourceContents;
-import org.apache.sling.mcp.server.spi.McpServerContribution;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-import org.osgi.service.component.runtime.ServiceComponentRuntime;
-import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
-
-@Component
-public class ComponentResourceContribution implements McpServerContribution {
-
-    private static String getStateString(int state) {
-        return switch (state) {
-            case 1 -> "UNSATISFIED_CONFIGURATION";
-            case 2 -> "UNSATISFIED_REFERENCE";
-            case 4 -> "SATISFIED";
-            case 8 -> "ACTIVE";
-            case 16 -> "REGISTERED";
-            case 32 -> "FACTORY";
-            case 64 -> "DISABLED";
-            case 128 -> "ENABLING";
-            case 256 -> "ENABLED";
-            case 512 -> "DISABLING";
-            default -> "UNKNOWN";
-        };
-    }
-
-    @Reference
-    private ServiceComponentRuntime scr;
-
-    @Activate
-    public ComponentResourceContribution() {}
-
-    @Override
-    public Optional<SyncResourceSpecification> getSyncResourceSpecification() {
-
-        return Optional.of(new 
McpStatelessServerFeatures.SyncResourceSpecification(
-                new Resource.Builder()
-                        .name("component")
-                        .uri("component://")
-                        .description("OSGi component status")
-                        .mimeType("text/plain")
-                        .build(),
-                (context, request) -> {
-                    Collection<ComponentDescriptionDTO> components = 
scr.getComponentDescriptionDTOs();
-                    String componentInfo = components.stream()
-                            .map(c -> {
-                                String state = 
scr.getComponentConfigurationDTOs(c).stream()
-                                        .map(config -> 
getStateString(config.state))
-                                        .collect(Collectors.joining(", "));
-                                return "Component " + c.name + " is in 
state(s): " + state;
-                            })
-                            .collect(Collectors.joining("\n"));
-
-                    TextResourceContents contents =
-                            new TextResourceContents("component://", 
"text/plain", componentInfo);
-
-                    return new McpSchema.ReadResourceResult(List.of(contents));
-                }));
-    }
-
-    @Override
-    public Optional<SyncResourceTemplateSpecification> 
getSyncResourceTemplateSpecification() {
-        return Optional.of(new 
McpStatelessServerFeatures.SyncResourceTemplateSpecification(
-                new ResourceTemplate.Builder()
-                        .uriTemplate("components://state/{state}")
-                        .name("components")
-                        .build(),
-                (context, request) -> {
-                    String componentInfo = "";
-                    String uri = request.uri().toLowerCase(Locale.ENGLISH);
-
-                    if (uri.startsWith("components://state/")) {
-                        String requestedState = 
uri.substring("components://state/".length());
-                        Collection<ComponentDescriptionDTO> components = 
scr.getComponentDescriptionDTOs();
-
-                        componentInfo = components.stream()
-                                .flatMap(c -> 
scr.getComponentConfigurationDTOs(c).stream()
-                                        .filter(config -> 
getStateString(config.state)
-                                                .toLowerCase(Locale.ENGLISH)
-                                                .equals(requestedState))
-                                        .map(config -> "Component " + c.name + 
" is in state: "
-                                                + 
getStateString(config.state)))
-                                .collect(Collectors.joining("\n"));
-                    }
-
-                    TextResourceContents contents =
-                            new TextResourceContents(request.uri(), 
"text/plain", componentInfo);
-
-                    return new ReadResourceResult(List.of(contents));
-                }));
-    }
-}
diff --git 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/LogToolContribution.java
 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/LogToolContribution.java
deleted file mode 100644
index 8e61147..0000000
--- 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/LogToolContribution.java
+++ /dev/null
@@ -1,359 +0,0 @@
-/*
- * 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 java.io.PrintWriter;
-import java.io.StringWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Optional;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-import io.modelcontextprotocol.json.McpJsonMapper;
-import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncToolSpecification;
-import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
-import io.modelcontextprotocol.spec.McpSchema.Tool;
-import org.apache.sling.mcp.server.spi.McpServerContribution;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-import org.osgi.service.log.LogEntry;
-import org.osgi.service.log.LogReaderService;
-import org.osgi.service.log.LogService;
-
-/**
- * MCP Tool that provides access to AEM/OSGi logs with filtering capabilities.
- * Allows filtering by regex pattern, log level, and maximum number of entries.
- */
-@Component
-public class LogToolContribution implements McpServerContribution {
-
-    @Reference
-    private LogReaderService logReaderService;
-
-    @Reference
-    private McpJsonMapper jsonMapper;
-
-    private static final int DEFAULT_MAX_LOGS = 200;
-    private static final SimpleDateFormat DATE_FORMAT = new 
SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
-
-    @Override
-    public Optional<SyncToolSpecification> getSyncToolSpecification() {
-
-        var schema = """
-                {
-                  "type" : "object",
-                  "id" : "urn:jsonschema:LogFilterInput",
-                  "properties" : {
-                    "regex" : {
-                      "type" : "string",
-                      "description" : "Optional regex pattern to filter log 
messages. If not provided, all logs are returned."
-                    },
-                    "logLevel" : {
-                      "type" : "string",
-                      "description" : "Minimum log level to return. Options: 
ERROR, WARN, INFO, DEBUG, TRACE. Defaults to ERROR.",
-                      "enum" : ["ERROR", "WARN", "INFO", "DEBUG", "TRACE"]
-                    },
-                    "maxEntries" : {
-                      "type" : "integer",
-                      "description" : "Maximum number of log entries to 
return. Defaults to 200.",
-                      "minimum" : 1,
-                      "maximum" : 1000
-                    }
-                  }
-                }
-                """;
-
-        return Optional.of(new SyncToolSpecification(
-                Tool.builder()
-                        .name("aem-logs")
-                        .description("Retrieve AEM/OSGi logs with optional 
filtering. "
-                                + "Supports filtering by regex pattern, log 
level (ERROR, WARN, INFO, DEBUG, TRACE), "
-                                + "and maximum number of entries. Returns most 
recent logs first.")
-                        .inputSchema(jsonMapper, schema)
-                        .build(),
-                (exchange, request) -> {
-                    String regexPattern = (String) 
request.arguments().get("regex");
-                    String logLevelStr = (String) 
request.arguments().get("logLevel");
-                    Object maxEntriesObj = 
request.arguments().get("maxEntries");
-
-                    // Parse parameters
-                    int maxEntries = DEFAULT_MAX_LOGS;
-                    if (maxEntriesObj instanceof Number) {
-                        maxEntries = ((Number) maxEntriesObj).intValue();
-                        maxEntries = Math.min(maxEntries, 1000); // Cap at 1000
-                    }
-
-                    int minLogLevel = LogService.LOG_ERROR;
-                    if (logLevelStr != null && !logLevelStr.isEmpty()) {
-                        minLogLevel = parseLogLevel(logLevelStr);
-                        if (minLogLevel == -1) {
-                            return CallToolResult.builder()
-                                    .addTextContent("Invalid log level: " + 
logLevelStr
-                                            + ". Valid options are: ERROR, 
WARN, INFO, DEBUG, TRACE")
-                                    .isError(true)
-                                    .build();
-                        }
-                    }
-
-                    // Compile regex pattern if provided
-                    Pattern pattern = null;
-                    if (regexPattern != null && !regexPattern.isEmpty()) {
-                        try {
-                            pattern = Pattern.compile(regexPattern, 
Pattern.CASE_INSENSITIVE);
-                        } catch (PatternSyntaxException e) {
-                            return CallToolResult.builder()
-                                    .addTextContent("Invalid regex pattern: " 
+ e.getMessage())
-                                    .isError(true)
-                                    .build();
-                        }
-                    }
-
-                    // Collect and filter logs
-                    List<LogEntry> filteredLogs = collectLogs(pattern, 
minLogLevel, maxEntries);
-
-                    // Format output
-                    String result = formatLogs(filteredLogs, regexPattern, 
minLogLevel, maxEntries);
-
-                    return 
CallToolResult.builder().addTextContent(result).build();
-                }));
-    }
-
-    private List<LogEntry> collectLogs(Pattern pattern, int minLogLevel, int 
maxEntries) {
-        List<LogEntry> logs = new ArrayList<>();
-
-        @SuppressWarnings("unchecked")
-        Enumeration<LogEntry> logEntries = logReaderService.getLog();
-        while (logEntries.hasMoreElements() && logs.size() < maxEntries) {
-            LogEntry entry = logEntries.nextElement();
-
-            // Filter by log level (lower values = higher severity)
-            if (entry.getLevel() > minLogLevel) {
-                continue;
-            }
-
-            // Filter by regex pattern if provided - search entire log entry
-            if (pattern != null) {
-                String fullLogEntry = buildFullLogEntryText(entry);
-                if (!pattern.matcher(fullLogEntry).find()) {
-                    continue;
-                }
-            }
-
-            logs.add(entry);
-        }
-
-        return logs;
-    }
-
-    private String buildFullLogEntryText(LogEntry entry) {
-        StringBuilder text = new StringBuilder();
-
-        // Add log level
-        text.append(getLogLevelName(entry.getLevel())).append(" ");
-
-        // Add bundle name
-        Bundle bundle = entry.getBundle();
-        if (bundle != null) {
-            text.append(getBundleName(bundle)).append(" ");
-        }
-
-        // Add message
-        String message = entry.getMessage();
-        if (message != null) {
-            text.append(message).append(" ");
-        }
-
-        // Add service reference info
-        ServiceReference<?> serviceRef = entry.getServiceReference();
-        if (serviceRef != null) {
-            String serviceDesc = getServiceDescription(serviceRef);
-            if (serviceDesc != null && !serviceDesc.isEmpty()) {
-                text.append(serviceDesc).append(" ");
-            }
-        }
-
-        // Add exception info
-        Throwable exception = entry.getException();
-        if (exception != null) {
-            text.append(exception.getClass().getName()).append(" ");
-            if (exception.getMessage() != null) {
-                text.append(exception.getMessage()).append(" ");
-            }
-
-            // Add stack trace
-            StringWriter sw = new StringWriter();
-            PrintWriter pw = new PrintWriter(sw);
-            exception.printStackTrace(pw);
-            text.append(sw.toString());
-        }
-
-        return text.toString();
-    }
-
-    private int parseLogLevel(String levelStr) {
-        return switch (levelStr.toUpperCase()) {
-            case "ERROR" -> LogService.LOG_ERROR;
-            case "WARN", "WARNING" -> LogService.LOG_WARNING;
-            case "INFO" -> LogService.LOG_INFO;
-            case "DEBUG" -> LogService.LOG_DEBUG;
-            default -> -1;
-        };
-    }
-
-    private String formatLogs(List<LogEntry> logs, String regexPattern, int 
minLogLevel, int maxEntries) {
-        StringBuilder result = new StringBuilder();
-
-        result.append("=== AEM Log Entries ===\n\n");
-        result.append("Filter Settings:\n");
-        result.append("  - Log Level: 
").append(getLogLevelName(minLogLevel)).append(" and higher severity\n");
-        result.append("  - Regex Pattern: ")
-                .append(regexPattern != null ? regexPattern : "(none)")
-                .append("\n");
-        result.append("  - Max Entries: ").append(maxEntries).append("\n");
-        result.append("  - Entries Found: 
").append(logs.size()).append("\n\n");
-
-        if (logs.isEmpty()) {
-            result.append("No log entries found matching the criteria.\n");
-            return result.toString();
-        }
-
-        result.append("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n");
-
-        for (int i = 0; i < logs.size(); i++) {
-            LogEntry entry = logs.get(i);
-            formatLogEntry(entry, i + 1, result);
-
-            if (i < logs.size() - 1) {
-                result.append("\n");
-            }
-        }
-
-        return result.toString();
-    }
-
-    private void formatLogEntry(LogEntry entry, int index, StringBuilder 
result) {
-        result.append("[").append(index).append("] ");
-        result.append(DATE_FORMAT.format(new Date(entry.getTime())));
-        result.append(" 
[").append(getLogLevelName(entry.getLevel())).append("] ");
-
-        // Add bundle information
-        Bundle bundle = entry.getBundle();
-        if (bundle != null) {
-            String bundleName = getBundleName(bundle);
-            result.append("[").append(bundleName).append("] ");
-        }
-
-        // Add message
-        String message = entry.getMessage();
-        result.append(message != null ? message : "(no message)");
-        result.append("\n");
-
-        // Add service reference info if available
-        ServiceReference<?> serviceRef = entry.getServiceReference();
-        if (serviceRef != null) {
-            String serviceDesc = getServiceDescription(serviceRef);
-            if (serviceDesc != null && !serviceDesc.isEmpty()) {
-                result.append("    Service: 
").append(serviceDesc).append("\n");
-            }
-        }
-
-        // Add exception info if available
-        Throwable exception = entry.getException();
-        if (exception != null) {
-            result.append("    Exception: 
").append(exception.getClass().getName());
-            if (exception.getMessage() != null) {
-                result.append(": ").append(exception.getMessage());
-            }
-            result.append("\n");
-
-            // Add stack trace (first few lines)
-            StringWriter sw = new StringWriter();
-            PrintWriter pw = new PrintWriter(sw);
-            exception.printStackTrace(pw);
-            String stackTrace = sw.toString();
-
-            // Limit stack trace to first 10 lines
-            String[] lines = stackTrace.split("\n");
-            int maxLines = Math.min(lines.length, 10);
-            for (int i = 0; i < maxLines; i++) {
-                result.append("      ").append(lines[i]).append("\n");
-            }
-            if (lines.length > maxLines) {
-                result.append("      ... (").append(lines.length - 
maxLines).append(" more lines)\n");
-            }
-        }
-    }
-
-    private String getBundleName(Bundle bundle) {
-        String name = bundle.getHeaders().get(Constants.BUNDLE_NAME);
-        if (name == null || name.isEmpty()) {
-            name = bundle.getSymbolicName();
-        }
-        if (name == null || name.isEmpty()) {
-            name = "Bundle#" + bundle.getBundleId();
-        }
-        return name;
-    }
-
-    private String getServiceDescription(ServiceReference<?> ref) {
-        if (ref == null) {
-            return null;
-        }
-
-        Object serviceId = ref.getProperty(Constants.SERVICE_ID);
-        Object objectClass = ref.getProperty(Constants.OBJECTCLASS);
-
-        StringBuilder desc = new StringBuilder();
-        if (objectClass instanceof String[]) {
-            String[] classes = (String[]) objectClass;
-            if (classes.length > 0) {
-                desc.append(classes[0]);
-                if (classes.length > 1) {
-                    desc.append(" (").append(classes.length - 1).append(" more 
interfaces)");
-                }
-            }
-        }
-
-        if (serviceId != null) {
-            if (desc.length() > 0) {
-                desc.append(" ");
-            }
-            desc.append("[id=").append(serviceId).append("]");
-        }
-
-        return desc.toString();
-    }
-
-    private String getLogLevelName(int level) {
-        return switch (level) {
-            case LogService.LOG_ERROR -> "ERROR";
-            case LogService.LOG_WARNING -> "WARN";
-            case LogService.LOG_INFO -> "INFO";
-            case LogService.LOG_DEBUG -> "DEBUG";
-            default -> "LEVEL_" + level;
-        };
-    }
-}
diff --git 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/OsgiBundleDiagnosticContribution.java
 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/OsgiBundleDiagnosticContribution.java
deleted file mode 100644
index fdc3be7..0000000
--- 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/OsgiBundleDiagnosticContribution.java
+++ /dev/null
@@ -1,525 +0,0 @@
-/*
- * 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 java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-import io.modelcontextprotocol.json.McpJsonMapper;
-import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncToolSpecification;
-import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
-import io.modelcontextprotocol.spec.McpSchema.Tool;
-import org.apache.sling.mcp.server.spi.McpServerContribution;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.wiring.BundleRequirement;
-import org.osgi.framework.wiring.BundleWire;
-import org.osgi.framework.wiring.BundleWiring;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-import org.osgi.service.component.runtime.ServiceComponentRuntime;
-import org.osgi.service.component.runtime.dto.ComponentConfigurationDTO;
-import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
-import org.osgi.service.component.runtime.dto.UnsatisfiedReferenceDTO;
-
-/**
- * MCP Tool that diagnoses why OSGi bundles and components aren't starting.
- * This tool provides detailed diagnostic information about:
- * - Bundle state and resolution issues
- * - Unsatisfied package imports
- * - Missing service dependencies for components
- * - Configuration problems
- */
-@Component
-public class OsgiBundleDiagnosticContribution implements McpServerContribution 
{
-
-    @Reference
-    private McpJsonMapper jsonMapper;
-
-    @Reference
-    private ServiceComponentRuntime scr;
-
-    private final BundleContext ctx;
-
-    @Activate
-    public OsgiBundleDiagnosticContribution(BundleContext ctx) {
-        this.ctx = ctx;
-    }
-
-    @Override
-    public Optional<SyncToolSpecification> getSyncToolSpecification() {
-
-        var schema = """
-                {
-                  "type" : "object",
-                  "id" : "urn:jsonschema:DiagnoseBundleInput",
-                  "properties" : {
-                    "bundleSymbolicName" : {
-                      "type" : "string",
-                      "description" : "The symbolic name of the bundle to 
diagnose. If not provided, will diagnose all problematic bundles."
-                    }
-                  }
-                }
-                """;
-
-        return Optional.of(new SyncToolSpecification(
-                Tool.builder()
-                        .name("diagnose-osgi-bundle")
-                        .description(
-                                "Diagnose why an OSGi bundle or component 
isn't starting. Provides detailed information about unsatisfied dependencies, 
missing packages, and component configuration issues.")
-                        .inputSchema(jsonMapper, schema)
-                        .build(),
-                (exchange, request) -> {
-                    String bundleSymbolicName = (String) 
request.arguments().get("bundleSymbolicName");
-
-                    if (bundleSymbolicName != null && 
!bundleSymbolicName.isEmpty()) {
-                        return diagnoseSpecificBundle(bundleSymbolicName);
-                    } else {
-                        return diagnoseAllProblematicBundles();
-                    }
-                }));
-    }
-
-    private CallToolResult diagnoseSpecificBundle(String symbolicName) {
-        Bundle bundle = findBundle(symbolicName);
-        if (bundle == null) {
-            return CallToolResult.builder()
-                    .addTextContent("Bundle '" + symbolicName + "' not found.")
-                    .isError(true)
-                    .build();
-        }
-
-        StringBuilder result = new StringBuilder();
-        result.append("=== Bundle Diagnostic Report ===\n\n");
-        result.append("Bundle: 
").append(bundle.getSymbolicName()).append("\n");
-        result.append("Version: ").append(bundle.getVersion()).append("\n");
-        result.append("State: 
").append(getStateName(bundle.getState())).append("\n\n");
-
-        if (bundle.getState() == Bundle.ACTIVE) {
-            result.append("✓ Bundle is ACTIVE and running normally.\n\n");
-            // Check components
-            appendComponentDiagnostics(bundle, result);
-        } else {
-            result.append("✗ Bundle is NOT active. Analyzing issues...\n\n");
-            analyzeBundleIssues(bundle, result);
-            appendComponentDiagnostics(bundle, result);
-        }
-
-        return 
CallToolResult.builder().addTextContent(result.toString()).build();
-    }
-
-    private CallToolResult diagnoseAllProblematicBundles() {
-        StringBuilder result = new StringBuilder();
-        result.append("=== OSGi System Diagnostic Report ===\n\n");
-
-        List<Bundle> problematicBundles = Arrays.stream(ctx.getBundles())
-                .filter(b -> b.getState() != Bundle.ACTIVE && b.getState() != 
Bundle.UNINSTALLED)
-                .collect(Collectors.toList());
-
-        if (problematicBundles.isEmpty()) {
-            result.append("✓ All bundles are active!\n\n");
-        } else {
-            result.append("Found ").append(problematicBundles.size()).append(" 
problematic bundle(s):\n\n");
-
-            for (Bundle bundle : problematicBundles) {
-                result.append("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
-                result.append("Bundle: 
").append(bundle.getSymbolicName()).append("\n");
-                result.append("State: 
").append(getStateName(bundle.getState())).append("\n");
-                analyzeBundleIssues(bundle, result);
-                result.append("\n");
-            }
-        }
-
-        // Check for components with issues
-        appendAllComponentIssues(result);
-
-        return 
CallToolResult.builder().addTextContent(result.toString()).build();
-    }
-
-    private void analyzeBundleIssues(Bundle bundle, StringBuilder result) {
-        if (bundle.getState() == Bundle.INSTALLED) {
-            result.append("\n⚠ Bundle is INSTALLED but not RESOLVED\n");
-            result.append("This typically means there are unsatisfied 
dependencies.\n\n");
-
-            boolean foundIssues = false;
-
-            // First try to get info from BundleWiring (for resolved 
requirements)
-            BundleWiring wiring = bundle.adapt(BundleWiring.class);
-            if (wiring != null) {
-                List<BundleRequirement> requirements = 
wiring.getRequirements(null);
-
-                for (BundleRequirement req : requirements) {
-                    List<BundleWire> wires = 
wiring.getRequiredWires(req.getNamespace());
-                    if (wires == null || wires.isEmpty()) {
-                        if (!foundIssues) {
-                            result.append("Unsatisfied Requirements:\n");
-                            foundIssues = true;
-                        }
-                        result.append("  ✗ ")
-                                .append(req.getNamespace())
-                                .append(": ")
-                                .append(req.getDirectives())
-                                .append("\n");
-
-                        // For package imports, show which package is missing
-                        if ("osgi.wiring.package".equals(req.getNamespace())) {
-                            String filter = req.getDirectives().get("filter");
-                            result.append("    Missing package: ")
-                                    .append(filter)
-                                    .append("\n");
-                        }
-                    }
-                }
-            }
-
-            // If no issues found via wiring, check manifest directly and 
compare with available exports
-            if (!foundIssues) {
-                String importPackage = 
bundle.getHeaders().get(Constants.IMPORT_PACKAGE);
-
-                if (importPackage != null && !importPackage.isEmpty()) {
-                    result.append("Analyzing Package Dependencies:\n\n");
-                    analyzePackageImports(importPackage, result);
-                    foundIssues = true;
-                }
-
-                String requireBundle = 
bundle.getHeaders().get(Constants.REQUIRE_BUNDLE);
-                if (requireBundle != null && !requireBundle.isEmpty()) {
-                    result.append("\nRequired Bundles (from manifest):\n");
-                    result.append("  ")
-                            .append(requireBundle.replace(",", ",\n  "))
-                            .append("\n\n");
-                    result.append("⚠ One or more of these bundles are not 
available or not in the correct state.\n");
-                    foundIssues = true;
-                }
-            }
-
-            if (!foundIssues) {
-                result.append("No dependency information found. Bundle may 
have internal errors.\n");
-            }
-        } else if (bundle.getState() == Bundle.RESOLVED) {
-            result.append("\n✓ Bundle is RESOLVED (all dependencies 
satisfied)\n");
-            result.append("Bundle can be started manually if it's not a 
fragment.\n");
-        } else if (bundle.getState() == Bundle.STARTING) {
-            result.append("\n⚠ Bundle is STARTING (stuck during 
activation)\n");
-            result.append("Check for errors in bundle activator or circular 
dependencies.\n");
-        }
-
-        // Check for fragment information
-        if ((bundle.getHeaders().get(Constants.FRAGMENT_HOST)) != null) {
-            result.append("\nNote: This is a fragment bundle (attached to: ")
-                    .append(bundle.getHeaders().get(Constants.FRAGMENT_HOST))
-                    .append(")\n");
-        }
-    }
-
-    /**
-     * Analyzes imported packages by checking if they are available in the 
OSGi environment.
-     * Scans all bundles to find which packages are exported and matches them 
against imports.
-     */
-    private void analyzePackageImports(String importPackageHeader, 
StringBuilder result) {
-        // Build a map of all exported packages in the system
-        java.util.Map<String, List<PackageExport>> exportedPackages = new 
java.util.HashMap<>();
-
-        for (Bundle b : ctx.getBundles()) {
-            if (b.getState() == Bundle.UNINSTALLED) {
-                continue;
-            }
-
-            String exportPackage = 
b.getHeaders().get(Constants.EXPORT_PACKAGE);
-            if (exportPackage != null && !exportPackage.isEmpty()) {
-                List<PackageInfo> exports = parsePackages(exportPackage);
-                for (PackageInfo pkg : exports) {
-                    if (pkg.name != null && !pkg.name.isEmpty() && 
pkg.name.contains(".")) {
-                        exportedPackages
-                                .computeIfAbsent(pkg.name, k -> new 
ArrayList<>())
-                                .add(new PackageExport(b, pkg.name, 
pkg.version));
-                    }
-                }
-            }
-        }
-
-        // Parse and check each imported package
-        List<PackageInfo> imports = parsePackages(importPackageHeader);
-        int missingCount = 0;
-        int availableCount = 0;
-        List<String> missingPackages = new ArrayList<>();
-
-        for (PackageInfo importPkg : imports) {
-            // Skip invalid package names
-            if (importPkg.name == null || importPkg.name.isEmpty() || 
!importPkg.name.contains(".")) {
-                continue;
-            }
-
-            List<PackageExport> availableExports = 
exportedPackages.get(importPkg.name);
-
-            if (availableExports == null || availableExports.isEmpty()) {
-                missingCount++;
-                missingPackages.add("  ✗ " + importPkg.name
-                        + (importPkg.version.isEmpty() ? "" : " " + 
importPkg.version)
-                        + (importPkg.optional ? " (optional)" : "") + "\n");
-            } else {
-                availableCount++;
-            }
-        }
-
-        // Only show missing packages
-        if (missingCount > 0) {
-            result.append("Missing Packages (")
-                    .append(missingCount)
-                    .append(" of ")
-                    .append(imports.size())
-                    .append(" imports):\n\n");
-            for (String missing : missingPackages) {
-                result.append(missing);
-            }
-            result.append(
-                    "\n⚠ Action Required: Install bundles that provide the 
missing packages, or downgrade / change the dependencies.\n");
-        } else {
-            result.append("✓ All ").append(imports.size()).append(" imported 
packages are available.\n");
-            result.append("Bundle should be resolvable. Check for other 
issues.\n");
-        }
-    }
-
-    /**
-     * Parse OSGi package header (Import-Package or Export-Package).
-     * Handles complex cases with version ranges, attributes, and directives.
-     */
-    private List<PackageInfo> parsePackages(String header) {
-        List<PackageInfo> packages = new ArrayList<>();
-        if (header == null || header.isEmpty()) {
-            return packages;
-        }
-
-        // State machine for parsing
-        StringBuilder current = new StringBuilder();
-        int depth = 0; // Track depth of quotes and brackets
-        boolean inQuotes = false;
-
-        for (int i = 0; i < header.length(); i++) {
-            char c = header.charAt(i);
-
-            if (c == '"') {
-                inQuotes = !inQuotes;
-                current.append(c);
-            } else if (!inQuotes && (c == '[' || c == '(')) {
-                depth++;
-                current.append(c);
-            } else if (!inQuotes && (c == ']' || c == ')')) {
-                depth--;
-                current.append(c);
-            } else if (c == ',' && depth == 0 && !inQuotes) {
-                // This is a package separator
-                String pkg = current.toString().trim();
-                if (!pkg.isEmpty()) {
-                    packages.add(parsePackageEntry(pkg));
-                }
-                current = new StringBuilder();
-            } else {
-                current.append(c);
-            }
-        }
-
-        // Don't forget the last package
-        String pkg = current.toString().trim();
-        if (!pkg.isEmpty()) {
-            packages.add(parsePackageEntry(pkg));
-        }
-
-        return packages;
-    }
-
-    /**
-     * Parse a single package entry like 
"com.example.pkg;version="[1.0,2.0)";resolution:=optional"
-     */
-    private PackageInfo parsePackageEntry(String entry) {
-        // Split on first semicolon to separate package name from attributes
-        int semicolonPos = entry.indexOf(';');
-        String packageName;
-        String attributes;
-
-        if (semicolonPos > 0) {
-            packageName = entry.substring(0, semicolonPos).trim();
-            attributes = entry.substring(semicolonPos + 1);
-        } else {
-            packageName = entry.trim();
-            attributes = "";
-        }
-
-        // Extract version
-        String version = "";
-        if (attributes.contains("version=")) {
-            int vStart = attributes.indexOf("version=") + 8;
-            int vEnd = attributes.length();
-            // Find the end of the version value (next semicolon outside 
quotes)
-            boolean inQuote = false;
-            for (int i = vStart; i < attributes.length(); i++) {
-                char c = attributes.charAt(i);
-                if (c == '"') {
-                    inQuote = !inQuote;
-                } else if (c == ';' && !inQuote) {
-                    vEnd = i;
-                    break;
-                }
-            }
-            version = attributes.substring(vStart, 
vEnd).trim().replaceAll("\"", "");
-        }
-
-        boolean optional = attributes.contains("resolution:=optional");
-
-        return new PackageInfo(packageName, version, optional);
-    }
-
-    private String extractVersion(String exportEntry) {
-        if (exportEntry.contains("version=")) {
-            int start = exportEntry.indexOf("version=") + 8;
-            int end = exportEntry.indexOf(";", start);
-            if (end == -1) {
-                end = exportEntry.length();
-            }
-            return exportEntry.substring(start, end).replaceAll("\"", 
"").trim();
-        }
-        return "0.0.0";
-    }
-
-    private static class PackageInfo {
-        final String name;
-        final String version;
-        final boolean optional;
-
-        PackageInfo(String name, String version, boolean optional) {
-            this.name = name;
-            this.version = version;
-            this.optional = optional;
-        }
-    }
-
-    private static class PackageExport {
-        final Bundle bundle;
-        final String packageName;
-        final String version;
-
-        PackageExport(Bundle bundle, String packageName, String version) {
-            this.bundle = bundle;
-            this.packageName = packageName;
-            this.version = version;
-        }
-    }
-
-    private void appendComponentDiagnostics(Bundle bundle, StringBuilder 
result) {
-        Collection<ComponentDescriptionDTO> components = 
scr.getComponentDescriptionDTOs(bundle);
-
-        if (components.isEmpty()) {
-            return;
-        }
-
-        result.append("\n--- Declarative Services Components ---\n\n");
-
-        for (ComponentDescriptionDTO desc : components) {
-            Collection<ComponentConfigurationDTO> configs = 
scr.getComponentConfigurationDTOs(desc);
-
-            result.append("Component: ").append(desc.name).append("\n");
-
-            if (configs.isEmpty()) {
-                result.append("  Status: Not configured/instantiated\n");
-            }
-
-            for (ComponentConfigurationDTO config : configs) {
-                result.append("  State: ")
-                        .append(getComponentStateName(config.state))
-                        .append("\n");
-
-                if (config.state == 
ComponentConfigurationDTO.UNSATISFIED_REFERENCE) {
-                    result.append("  ✗ Unsatisfied Service References:\n");
-                    for (UnsatisfiedReferenceDTO ref : 
config.unsatisfiedReferences) {
-                        result.append("    - ")
-                                .append(ref.name)
-                                .append(" (")
-                                .append(ref.target)
-                                .append(")\n");
-                    }
-                } else if (config.state == 
ComponentConfigurationDTO.UNSATISFIED_CONFIGURATION) {
-                    result.append("  ✗ Missing required configuration\n");
-                } else if (config.state == ComponentConfigurationDTO.SATISFIED
-                        || config.state == ComponentConfigurationDTO.ACTIVE) {
-                    result.append("  ✓ Component is working correctly\n");
-                }
-            }
-            result.append("\n");
-        }
-    }
-
-    private void appendAllComponentIssues(StringBuilder result) {
-        List<ComponentDescriptionDTO> allComponents = new 
ArrayList<>(scr.getComponentDescriptionDTOs());
-        List<String> problematicComponents = new ArrayList<>();
-
-        for (ComponentDescriptionDTO desc : allComponents) {
-            Collection<ComponentConfigurationDTO> configs = 
scr.getComponentConfigurationDTOs(desc);
-
-            for (ComponentConfigurationDTO config : configs) {
-                if (config.state == 
ComponentConfigurationDTO.UNSATISFIED_REFERENCE
-                        || config.state == 
ComponentConfigurationDTO.UNSATISFIED_CONFIGURATION) {
-                    problematicComponents.add(desc.name + " (" + 
getComponentStateName(config.state) + ")");
-                }
-            }
-        }
-
-        if (!problematicComponents.isEmpty()) {
-            result.append("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
-            result.append("Problematic Components:\n\n");
-            for (String comp : problematicComponents) {
-                result.append("  ✗ ").append(comp).append("\n");
-            }
-        }
-    }
-
-    private Bundle findBundle(String symbolicName) {
-        return Arrays.stream(ctx.getBundles())
-                .filter(b -> b.getSymbolicName().equals(symbolicName))
-                .findFirst()
-                .orElse(null);
-    }
-
-    private String getStateName(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 (" + state + ")";
-        };
-    }
-
-    private String getComponentStateName(int state) {
-        return switch (state) {
-            case ComponentConfigurationDTO.UNSATISFIED_CONFIGURATION -> 
"UNSATISFIED_CONFIGURATION";
-            case ComponentConfigurationDTO.UNSATISFIED_REFERENCE -> 
"UNSATISFIED_REFERENCE";
-            case ComponentConfigurationDTO.SATISFIED -> "SATISFIED";
-            case ComponentConfigurationDTO.ACTIVE -> "ACTIVE";
-            default -> "UNKNOWN (" + state + ")";
-        };
-    }
-}
diff --git 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/OsgiDiagnosticPromptContribution.java
 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/OsgiDiagnosticPromptContribution.java
deleted file mode 100644
index 10e6e38..0000000
--- 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/OsgiDiagnosticPromptContribution.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * 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 java.util.List;
-import java.util.Optional;
-
-import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncPromptSpecification;
-import io.modelcontextprotocol.spec.McpSchema;
-import io.modelcontextprotocol.spec.McpSchema.GetPromptResult;
-import io.modelcontextprotocol.spec.McpSchema.Prompt;
-import io.modelcontextprotocol.spec.McpSchema.PromptArgument;
-import io.modelcontextprotocol.spec.McpSchema.PromptMessage;
-import io.modelcontextprotocol.spec.McpSchema.Role;
-import org.apache.sling.mcp.server.spi.McpServerContribution;
-import org.osgi.service.component.annotations.Component;
-
-/**
- * MCP Prompt that helps developers diagnose and fix OSGi bundle issues.
- * This prompt teaches Cursor how to:
- * - Identify why bundles aren't starting
- * - Understand common OSGi issues
- * - Provide actionable solutions
- */
-@Component
-public class OsgiDiagnosticPromptContribution implements McpServerContribution 
{
-
-    @Override
-    public Optional<SyncPromptSpecification> getSyncPromptSpecification() {
-        return Optional.of(new SyncPromptSpecification(
-                new Prompt(
-                        "diagnose-osgi-issue",
-                        "Diagnose OSGi Bundle Issues",
-                        "Helps diagnose why an OSGi bundle or component isn't 
starting in AEM/Sling. Provides step-by-step troubleshooting guidance.",
-                        List.of(new PromptArgument(
-                                "bundle-name",
-                                "Bundle Symbolic Name",
-                                "The symbolic name of the bundle that isn't 
starting (optional - if not provided, will check all bundles)",
-                                false))),
-                (context, request) -> {
-                    String bundleName = (String) 
request.arguments().get("bundle-name");
-
-                    String instructions = 
buildDiagnosticInstructions(bundleName);
-
-                    PromptMessage msg = new PromptMessage(Role.ASSISTANT, new 
McpSchema.TextContent(instructions));
-
-                    return new GetPromptResult("OSGi Diagnostic Guide", 
List.of(msg));
-                }));
-    }
-
-    private String buildDiagnosticInstructions(String bundleName) {
-        StringBuilder sb = new StringBuilder();
-
-        sb.append("# OSGi Bundle Diagnostic Assistant\n\n");
-
-        if (bundleName != null && !bundleName.isEmpty()) {
-            sb.append("I'll help you diagnose why the bundle '")
-                    .append(bundleName)
-                    .append("' isn't starting.\n\n");
-            sb.append("## Step 1: Run Diagnostic Tool\n\n");
-            sb.append("First, use the `diagnose-osgi-bundle` tool with 
bundleSymbolicName='")
-                    .append(bundleName)
-                    .append("'\n\n");
-        } else {
-            sb.append("I'll help you diagnose OSGi bundle issues in your 
AEM/Sling environment.\n\n");
-            sb.append("## Step 1: Identify Problematic Bundles\n\n");
-            sb.append("Use the `diagnose-osgi-bundle` tool without parameters 
to scan all bundles.\n\n");
-        }
-
-        sb.append("## Step 2: Interpret Common Issues\n\n");
-
-        sb.append("### Bundle State: INSTALLED (Not Resolved)\n");
-        sb.append("**Problem**: Bundle dependencies aren't satisfied.\n\n");
-        sb.append("**Common Causes**:\n");
-        sb.append(
-                "- Missing package imports: Another bundle that exports the 
required package isn't installed or active\n");
-        sb.append("- Version conflicts: Required package version doesn't match 
available versions\n");
-        sb.append("- Missing bundle: A required bundle hasn't been 
deployed\n\n");
-        sb.append("**Solutions**:\n");
-        sb.append("1. Check the diagnostic output for 'Unsatisfied 
Requirements'\n");
-        sb.append("2. Look for the missing packages in the Import-Package 
errors\n");
-        sb.append("3. Use `bundle://` resource to find bundles that export the 
needed packages\n");
-        sb.append("4. Install missing bundles or update bundle manifests to 
match available versions\n\n");
-
-        sb.append("### Bundle State: RESOLVED (Not Active)\n");
-        sb.append("**Problem**: Bundle has all dependencies but hasn't been 
started.\n\n");
-        sb.append("**Common Causes**:\n");
-        sb.append("- Bundle has lazy activation policy\n");
-        sb.append("- Bundle is a fragment (fragments never become ACTIVE)\n");
-        sb.append("- Manual start required\n\n");
-        sb.append("**Solutions**:\n");
-        sb.append("1. If it's not a fragment, the bundle might just need to be 
started\n");
-        sb.append("2. Check if the bundle has Bundle-ActivationPolicy: 
lazy\n");
-        sb.append("3. Fragments are normal - they attach to their host 
bundle\n\n");
-
-        sb.append("### Component State: UNSATISFIED_REFERENCE\n");
-        sb.append("**Problem**: Component can't find required OSGi 
services.\n\n");
-        sb.append("**Common Causes**:\n");
-        sb.append("- Required service isn't registered (bundle providing it 
isn't active)\n");
-        sb.append("- Service filter doesn't match any available services\n");
-        sb.append("- Circular dependency between components\n\n");
-        sb.append("**Solutions**:\n");
-        sb.append("1. Check the 'Unsatisfied Service References' in the 
diagnostic output\n");
-        sb.append("2. Use `component://` resource to verify the service 
provider is active\n");
-        sb.append("3. Check if the target filter is too restrictive\n");
-        sb.append("4. Make the reference optional if possible 
(cardinality=OPTIONAL)\n\n");
-
-        sb.append("### Component State: UNSATISFIED_CONFIGURATION\n");
-        sb.append("**Problem**: Component requires configuration that hasn't 
been provided.\n\n");
-        sb.append("**Common Causes**:\n");
-        sb.append("- Missing OSGi configuration in /apps or /libs\n");
-        sb.append("- Configuration not deployed to the environment\n");
-        sb.append("- Wrong configuration PID\n\n");
-        sb.append("**Solutions**:\n");
-        sb.append("1. Check if component has @Designate annotation requiring 
config\n");
-        sb.append("2. Verify configuration exists in repository or as .config 
file\n");
-        sb.append("3. Check configuration PID matches component name\n");
-        sb.append("4. Make configuration optional by removing 'required' 
policy\n\n");
-
-        sb.append("## Step 3: Apply Fixes\n\n");
-        sb.append("Based on the diagnostic results, I'll help you:\n");
-        sb.append("1. Identify which dependencies need to be added to your 
pom.xml\n");
-        sb.append("2. Fix package import/export statements in bnd.bnd or 
MANIFEST.MF\n");
-        sb.append("3. Create missing OSGi configurations\n");
-        sb.append("4. Update component annotations to fix reference issues\n");
-        sb.append("5. Suggest architectural changes if circular dependencies 
are detected\n\n");
-
-        sb.append("## Step 4: Verify Fix\n\n");
-        sb.append("After applying fixes:\n");
-        sb.append("1. Rebuild and redeploy the bundle\n");
-        sb.append("2. Run the diagnostic tool again to verify all issues are 
resolved\n");
-        sb.append("3. Check that the bundle state is ACTIVE\n");
-        sb.append("4. Verify components are in ACTIVE or SATISFIED state\n\n");
-
-        sb.append("Let me know what the diagnostic tool returns, and I'll 
provide specific solutions for your issue.");
-
-        return sb.toString();
-    }
-}
diff --git 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/RecentRequestsContribution.java
 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/RecentRequestsContribution.java
deleted file mode 100644
index d4149b2..0000000
--- 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/RecentRequestsContribution.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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 java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
-
-import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncResourceSpecification;
-import io.modelcontextprotocol.spec.McpSchema.ReadResourceResult;
-import io.modelcontextprotocol.spec.McpSchema.Resource;
-import io.modelcontextprotocol.spec.McpSchema.TextResourceContents;
-import org.apache.sling.engine.RequestInfo;
-import org.apache.sling.engine.RequestInfoProvider;
-import org.apache.sling.mcp.server.spi.McpServerContribution;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-
-@Component
-public class RecentRequestsContribution implements McpServerContribution {
-
-    private RequestInfoProvider requestInfoProvider;
-
-    @Activate
-    public RecentRequestsContribution(@Reference RequestInfoProvider 
requestInfoProvider) {
-        this.requestInfoProvider = requestInfoProvider;
-    }
-
-    private String describe(RequestInfo ri) {
-        return "Id: " + ri.getId() + "\n" + "Method: "
-                + ri.getMethod() + "\n" + "Path: " + ri.getPath() + "\n" + 
"User id: " + ri.getUserId() + "\n"
-                + ":\n" + ri.getLog();
-    }
-
-    @Override
-    public Optional<SyncResourceSpecification> getSyncResourceSpecification() {
-        return Optional.of(new SyncResourceSpecification(
-                new Resource.Builder()
-                        .uri("recent-requests://all")
-                        .description(
-                                "Prints all recent requests ( excluding 
/bin/mcp ). Contains information about method, path, user id and a verbose log 
of internal operations, including authentication, resource resolution, script 
resolution, nested scripts/servlets and filters.")
-                        .name("recent-requests-all")
-                        .build(),
-                (context, request) -> {
-                    String allRequests = StreamSupport.stream(
-                                    
requestInfoProvider.getRequestInfos().spliterator(), false)
-                            .filter((ri) -> !ri.getPath().equals("/bin/mcp"))
-                            .map(this::describe)
-                            .collect(Collectors.joining("\n\n" + 
"-".repeat(20) + "\n\n"));
-
-                    return new ReadResourceResult(
-                            List.of(new 
TextResourceContents("recent-requests://all", "text/plain", allRequests)));
-                }));
-    }
-}
diff --git 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/RefreshPackagesContribution.java
 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/RefreshPackagesContribution.java
deleted file mode 100644
index 1e0b9ae..0000000
--- 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/RefreshPackagesContribution.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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 java.util.Optional;
-
-import io.modelcontextprotocol.json.McpJsonMapper;
-import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncToolSpecification;
-import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
-import io.modelcontextprotocol.spec.McpSchema.Tool;
-import org.apache.sling.mcp.server.spi.McpServerContribution;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.wiring.FrameworkWiring;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-
-@Component
-public class RefreshPackagesContribution implements McpServerContribution {
-
-    @Reference
-    private McpJsonMapper jsonMapper;
-
-    private final BundleContext ctx;
-
-    @Activate
-    public RefreshPackagesContribution(BundleContext ctx) {
-        this.ctx = ctx;
-    }
-
-    @Override
-    public Optional<SyncToolSpecification> getSyncToolSpecification() {
-
-        var schema = """
-                {
-                  "type" : "object",
-                  "id" : "urn:jsonschema:Operation",
-                  "properties" : { }
-                }
-                """;
-
-        return Optional.of(new SyncToolSpecification(
-                Tool.builder()
-                        .name("refresh-packages")
-                        .description("Refresh Packages")
-                        .inputSchema(jsonMapper, schema)
-                        .build(),
-                (exchange, arguments) -> {
-                    FrameworkWiring fw = 
ctx.getBundle(0).adapt(FrameworkWiring.class);
-
-                    fw.refreshBundles(null);
-
-                    return CallToolResult.builder()
-                            .addTextContent("Bundles refreshed successfully")
-                            .build();
-                }));
-    }
-}
diff --git 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/ServletPromptContribution.java
 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/ServletPromptContribution.java
deleted file mode 100644
index cb2f181..0000000
--- 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/ServletPromptContribution.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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 java.util.List;
-import java.util.Optional;
-
-import io.modelcontextprotocol.server.McpStatelessServerFeatures;
-import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncCompletionSpecification;
-import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncPromptSpecification;
-import io.modelcontextprotocol.spec.McpSchema;
-import 
io.modelcontextprotocol.spec.McpSchema.CompleteResult.CompleteCompletion;
-import io.modelcontextprotocol.spec.McpSchema.Prompt;
-import io.modelcontextprotocol.spec.McpSchema.PromptArgument;
-import io.modelcontextprotocol.spec.McpSchema.PromptMessage;
-import io.modelcontextprotocol.spec.McpSchema.Role;
-import org.apache.sling.mcp.server.spi.McpServerContribution;
-import org.osgi.service.component.annotations.Component;
-
-@Component
-public class ServletPromptContribution implements McpServerContribution {
-
-    @Override
-    public Optional<SyncPromptSpecification> getSyncPromptSpecification() {
-        return Optional.of(new SyncPromptSpecification(
-                new Prompt(
-                        "new-sling-servlet",
-                        "Create new Sling Servlet",
-                        "Creates a new Sling Servlet in the current project 
using annotations",
-                        List.of(new PromptArgument(
-                                "resource-type",
-                                "Resource type",
-                                "The Sling resource type to bind this servlet 
to.",
-                                true))),
-                (context, request) -> {
-                    String resourceType = (String) 
request.arguments().get("resource-type");
-                    PromptMessage msg = new PromptMessage(
-                            Role.ASSISTANT,
-                            new McpSchema.TextContent(
-                                    "Create a new Sling Servlet for resource 
type: "
-                                            + resourceType
-                                            + " . Use the Sling-specific OSGi 
declarative services annotations - @SlingServletResourceTypes and @Component . 
Configure by default with the GET method and the json extension. Provide a 
basic implementation of the doGet method that returns a JSON response with a 
message 'Hello from Sling Servlet at resource type <resource-type>'."));
-                    return new McpSchema.GetPromptResult("Result of creation", 
List.of(msg));
-                }));
-    }
-
-    @Override
-    public Optional<SyncCompletionSpecification> 
getSyncCompletionSpecification() {
-        // supply no completions for various resource types because it's 
supposed to be specified by the user
-        return Optional.of(new 
McpStatelessServerFeatures.SyncCompletionSpecification(
-                new McpSchema.PromptReference("ref/prompt", 
"new-sling-servlet"), (context, request) -> {
-                    return new McpSchema.CompleteResult(new 
CompleteCompletion(List.of(), 0, false));
-                }));
-    }
-}
diff --git a/src/main/java/org/apache/sling/mcp/server/spi/package-info.java 
b/src/main/java/org/apache/sling/mcp/server/spi/package-info.java
index 13b7525..aec19a4 100644
--- a/src/main/java/org/apache/sling/mcp/server/spi/package-info.java
+++ b/src/main/java/org/apache/sling/mcp/server/spi/package-info.java
@@ -1,2 +1,20 @@
+/*
+ * 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.
+ */
 @org.osgi.annotation.versioning.Version("1.0.0")
 package org.apache.sling.mcp.server.spi;

Reply via email to