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 feb8a9a60634287d8fb6f2983c517b1871a035a8
Author: Niek Raaijmakers <[email protected]>
AuthorDate: Thu Dec 11 14:24:55 2025 +0100

    mcp bundle diagnoser, component status, logs (#118)
    
    Co-authored-by: Niek Raaijmakers <[email protected]>
---
 pom.xml                                            |  11 +
 .../contribs/ComponentResourceContribution.java    | 126 +++++
 .../server/impl/contribs/LogToolContribution.java  | 355 ++++++++++++++
 .../contribs/OsgiBundleDiagnosticContribution.java | 522 +++++++++++++++++++++
 .../contribs/OsgiDiagnosticPromptContribution.java | 155 ++++++
 5 files changed, 1169 insertions(+)

diff --git a/pom.xml b/pom.xml
index d2c09fb..edca093 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,6 +62,11 @@
             <artifactId>org.osgi.service.component.annotations</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component</artifactId>
+            <scope>provided</scope>
+        </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.service.metatype.annotations</artifactId>
@@ -100,6 +105,12 @@
             <version>0.17.0</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.log</artifactId>
+            <version>1.3.0</version>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
     <build>
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
new file mode 100644
index 0000000..7a307fd
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/ComponentResourceContribution.java
@@ -0,0 +1,126 @@
+/*
+ * 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.impl.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
new file mode 100644
index 0000000..9e68661
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/LogToolContribution.java
@@ -0,0 +1,355 @@
+/*
+ * 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.impl.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 new CallToolResult(
+                                    "Invalid log level: " + logLevelStr
+                                            + ". Valid options are: ERROR, 
WARN, INFO, DEBUG, TRACE",
+                                    Boolean.TRUE);
+                        }
+                    }
+
+                    // 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 new CallToolResult("Invalid regex pattern: 
" + e.getMessage(), Boolean.TRUE);
+                        }
+                    }
+
+                    // Collect and filter logs
+                    List<LogEntry> filteredLogs = collectLogs(pattern, 
minLogLevel, maxEntries);
+
+                    // Format output
+                    String result = formatLogs(filteredLogs, regexPattern, 
minLogLevel, maxEntries);
+
+                    return new CallToolResult(result, Boolean.FALSE);
+                }));
+    }
+
+    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
new file mode 100644
index 0000000..a4affef
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/OsgiBundleDiagnosticContribution.java
@@ -0,0 +1,522 @@
+/*
+ * 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.impl.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 new CallToolResult("Bundle '" + symbolicName + "' not 
found.", Boolean.TRUE);
+        }
+
+        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 new CallToolResult(result.toString(), Boolean.FALSE);
+    }
+
+    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 new CallToolResult(result.toString(), Boolean.FALSE);
+    }
+
+    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
new file mode 100644
index 0000000..25ea95b
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/OsgiDiagnosticPromptContribution.java
@@ -0,0 +1,155 @@
+/*
+ * 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.impl.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();
+    }
+}

Reply via email to