This is an automated email from the ASF dual-hosted git repository.
rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git
The following commit(s) were added to refs/heads/master by this push:
new c2ad0487 chore(mcp-server-contribution): project was moved
c2ad0487 is described below
commit c2ad0487b498c2a30ba96f780ee3818bb421c3dc
Author: Robert Munteanu <[email protected]>
AuthorDate: Mon May 4 15:06:00 2026 +0200
chore(mcp-server-contribution): project was moved
---
mcp-server-contributions/README.md | 4 +-
mcp-server-contributions/bnd.bnd | 1 -
mcp-server-contributions/pom.xml | 164 -------
.../server/impl/contribs/LogToolContribution.java | 228 ---------
.../contribs/OsgiBundleDiagnosticContribution.java | 524 ---------------------
.../impl/contribs/RecentRequestsContribution.java | 108 -----
.../server/impl/contribs/internal/LogSnapshot.java | 42 --
.../contribs/internal/StructuredLogBuffer.java | 100 ----
.../internal/StructuredLogBufferAppender.java | 173 -------
.../libs/sling/mcp/prompts/new-sling-servlet.md | 17 -
.../libs/sling/mcp/prompts/troubleshoot.md | 45 --
.../internal/StructuredLogBufferAppenderTest.java | 62 ---
.../contribs/internal/StructuredLogBufferTest.java | 64 ---
13 files changed, 1 insertion(+), 1531 deletions(-)
diff --git a/mcp-server-contributions/README.md
b/mcp-server-contributions/README.md
index 0f037be0..2cb8c933 100644
--- a/mcp-server-contributions/README.md
+++ b/mcp-server-contributions/README.md
@@ -1,3 +1 @@
-# Apache Sling MCP Server contributions
-
-This project contains contributions ( tools, resources, prompts) for the
Apache Sling MCP Server. See [the Sling MCP Server
repository](https://github.com/apache/sling-org-apache-sling-mcp-server) for
details on how to set up and run the MCP Server itself.
+Moved to
[https://github.com/apache/sling-org-apache-sling-mcp-server-contributions](https://github.com/apache/sling-org-apache-sling-mcp-server-contributions)
diff --git a/mcp-server-contributions/bnd.bnd b/mcp-server-contributions/bnd.bnd
deleted file mode 100644
index 5a8e9c40..00000000
--- a/mcp-server-contributions/bnd.bnd
+++ /dev/null
@@ -1 +0,0 @@
-Sling-Initial-Content:
SLING-INF/libs/sling/mcp/prompts;path:=/libs/sling/mcp/prompts;overwrite:=true
diff --git a/mcp-server-contributions/pom.xml b/mcp-server-contributions/pom.xml
deleted file mode 100644
index e5bbd31c..00000000
--- a/mcp-server-contributions/pom.xml
+++ /dev/null
@@ -1,164 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
-
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.apache.sling</groupId>
- <artifactId>sling-bundle-parent</artifactId>
- <version>65</version>
- <relativePath />
- </parent>
-
- <artifactId>org.apache.sling.mcp-server-contributions</artifactId>
- <version>0.1.0-SNAPSHOT</version>
-
- <name>Apache Sling MCP Server Contributions</name>
-
- <properties>
- <sling.java.version>17</sling.java.version>
- <!-- Run with
-Dslingfeature.app.vmOption=-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=30303
for debug mode-->
- <slingfeature.app.vmOption />
- </properties>
-
- <dependencies>
- <dependency>
- <groupId>org.osgi</groupId>
- <artifactId>org.osgi.framework</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.osgi</groupId>
- <artifactId>org.osgi.resource</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.osgi</groupId>
- <artifactId>org.osgi.annotation.bundle</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.osgi</groupId>
- <artifactId>org.osgi.annotation.versioning</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.osgi</groupId>
- <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>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.servlets.annotations</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.jetbrains</groupId>
- <artifactId>annotations</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.api</artifactId>
- <version>3.0.2</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.engine</artifactId>
- <version>2.10.2</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>jakarta.servlet</groupId>
- <artifactId>jakarta.servlet-api</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>io.modelcontextprotocol.sdk</groupId>
- <artifactId>mcp-core</artifactId>
- <version>1.0.0</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- <version>1.7.32</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-core</artifactId>
- <version>1.2.13</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-classic</artifactId>
- <version>1.2.13</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.mcp-server</artifactId>
- <version>0.1.2</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.junit.jupiter</groupId>
- <artifactId>junit-jupiter-api</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.junit.jupiter</groupId>
- <artifactId>junit-jupiter-engine</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.osgi</groupId>
- <artifactId>org.osgi.util.converter</artifactId>
- <scope>test</scope>
- </dependency>
- </dependencies>
-
- <build>
- <plugins>
- <!-- Configure bnd-baseline to skip when no previous version
exists. Remove after first release -->
- <plugin>
- <groupId>biz.aQute.bnd</groupId>
- <artifactId>bnd-baseline-maven-plugin</artifactId>
- <configuration>
- <failOnMissing>false</failOnMissing>
- </configuration>
- </plugin>
- </plugins>
- </build>
-
-</project>
diff --git
a/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/LogToolContribution.java
b/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/LogToolContribution.java
deleted file mode 100644
index 0ec2db36..00000000
---
a/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/LogToolContribution.java
+++ /dev/null
@@ -1,228 +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.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-import ch.qos.logback.classic.Level;
-import io.modelcontextprotocol.json.McpJsonMapperSupplier;
-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.contribs.internal.LogSnapshot;
-import
org.apache.sling.mcp.server.impl.contribs.internal.StructuredLogBufferAppender;
-import org.apache.sling.mcp.server.spi.McpServerContribution;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-
-/**
- * MCP Tool that provides access to logs with filtering capabilities.
- * Allows filtering by regex pattern, log level, and maximum number of entries.
- */
-@Component
-public class LogToolContribution implements McpServerContribution {
-
- @Reference
- private StructuredLogBufferAppender structuredLogBufferAppender;
-
- @Reference
- private McpJsonMapperSupplier 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 List<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 List.of(new SyncToolSpecification(
- Tool.builder()
- .name("logs")
- .description("Retrieve 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.get(), 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
- }
-
- Level minLogLevel = Level.ERROR;
- if (logLevelStr != null && !logLevelStr.isEmpty()) {
- minLogLevel = parseLogLevel(logLevelStr);
- if (minLogLevel == null) {
- 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();
- }
- }
-
- List<LogSnapshot> filteredLogs =
-
structuredLogBufferAppender.getBuffer().getRecent(pattern, minLogLevel,
maxEntries);
-
- // Format output
- String result = formatLogs(filteredLogs, regexPattern,
minLogLevel, maxEntries);
-
- return
CallToolResult.builder().addTextContent(result).build();
- }));
- }
-
- private Level parseLogLevel(String levelStr) {
- return switch (levelStr.toUpperCase()) {
- case "ERROR" -> Level.ERROR;
- case "WARN", "WARNING" -> Level.WARN;
- case "INFO" -> Level.INFO;
- case "DEBUG" -> Level.DEBUG;
- case "TRACE" -> Level.TRACE;
- default -> null;
- };
- }
-
- private String formatLogs(List<LogSnapshot> logs, String regexPattern,
Level minLogLevel, int maxEntries) {
- StringBuilder result = new StringBuilder();
-
- result.append("=== 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++) {
- LogSnapshot entry = logs.get(i);
- formatLogEntry(entry, i + 1, result);
-
- if (i < logs.size() - 1) {
- result.append("\n");
- }
- }
-
- return result.toString();
- }
-
- private void formatLogEntry(LogSnapshot entry, int index, StringBuilder
result) {
- result.append("[").append(index).append("] ");
- result.append(DATE_FORMAT.format(new Date(entry.timeMillis())));
- result.append(" [").append(getLogLevelName(entry.level())).append("]
");
- result.append("[")
- .append(entry.loggerName() != null ? entry.loggerName() :
"(unknown logger)")
- .append("] ");
-
- String message = entry.formattedMessage();
- result.append(message != null ? message : "(no message)");
- result.append("\n");
-
- if (entry.threadName() != null && !entry.threadName().isEmpty()) {
- result.append(" Thread:
").append(entry.threadName()).append("\n");
- }
-
- if (!entry.mdc().isEmpty()) {
- result.append(" MDC:
").append(formatMdc(entry.mdc())).append("\n");
- }
-
- String throwableText = entry.throwableText();
- if (throwableText != null && !throwableText.isEmpty()) {
- String[] lines = throwableText.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 formatMdc(Map<String, String> mdc) {
- StringBuilder result = new StringBuilder();
- boolean first = true;
- for (Map.Entry<String, String> entry : mdc.entrySet()) {
- if (!first) {
- result.append(", ");
- }
- result.append(entry.getKey()).append('=').append(entry.getValue());
- first = false;
- }
- return result.toString();
- }
-
- private String getLogLevelName(Level level) {
- return level != null ? level.levelStr : "UNKNOWN";
- }
-}
diff --git
a/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/OsgiBundleDiagnosticContribution.java
b/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/OsgiBundleDiagnosticContribution.java
deleted file mode 100644
index 6abb702d..00000000
---
a/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/OsgiBundleDiagnosticContribution.java
+++ /dev/null
@@ -1,524 +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.stream.Collectors;
-
-import io.modelcontextprotocol.json.McpJsonMapperSupplier;
-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 McpJsonMapperSupplier jsonMapperSupplier;
-
- @Reference
- private ServiceComponentRuntime scr;
-
- private final BundleContext ctx;
-
- @Activate
- public OsgiBundleDiagnosticContribution(BundleContext ctx) {
- this.ctx = ctx;
- }
-
- @Override
- public List<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 List.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(jsonMapperSupplier.get(), 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/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/RecentRequestsContribution.java
b/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/RecentRequestsContribution.java
deleted file mode 100644
index 148b6cc9..00000000
---
a/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/RecentRequestsContribution.java
+++ /dev/null
@@ -1,108 +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.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
-
-import io.modelcontextprotocol.json.McpJsonMapperSupplier;
-import
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncToolSpecification;
-import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
-import io.modelcontextprotocol.spec.McpSchema.Tool;
-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 final RequestInfoProvider requestInfoProvider;
- private final McpJsonMapperSupplier jsonMapperSupplier;
-
- @Activate
- public RecentRequestsContribution(
- @Reference RequestInfoProvider requestInfoProvider, @Reference
McpJsonMapperSupplier jsonMapperSupplier) {
- this.requestInfoProvider = requestInfoProvider;
- this.jsonMapperSupplier = jsonMapperSupplier;
- }
-
- 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 List<SyncToolSpecification> getSyncToolSpecification() {
-
- var schema = """
- {
- "type" : "object",
- "id" : "urn:jsonschema:RecentRequestsInput",
- "properties" : {
- "path" : {
- "type" : "string",
- "description" : "Optional regex pattern to filter
requests by path. If not provided, all recent requests are returned."
- }
- }
- }
- """;
-
- return List.of(new SyncToolSpecification(
- Tool.builder()
- .name("recent-requests")
- .description(
- "Returns all recent requests matching the
specified path regex. "
- + "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.")
- .inputSchema(jsonMapperSupplier.get(), schema)
- .build(),
- (exchange, request) -> {
- String pathRegex = (String)
request.arguments().get("path");
-
- Pattern pattern = null;
- if (pathRegex != null && !pathRegex.isEmpty()) {
- try {
- pattern = Pattern.compile(pathRegex,
Pattern.CASE_INSENSITIVE);
- } catch (PatternSyntaxException e) {
- return CallToolResult.builder()
- .addTextContent("Invalid regex pattern: "
+ e.getMessage())
- .isError(true)
- .build();
- }
- }
-
- final Pattern finalPattern = pattern;
- String allRequests = StreamSupport.stream(
-
requestInfoProvider.getRequestInfos().spliterator(), false)
- .filter(ri -> finalPattern == null
- ||
finalPattern.matcher(ri.getPath()).find())
- .map(this::describe)
- .collect(Collectors.joining("\n\n" +
"-".repeat(20) + "\n\n"));
-
- return
CallToolResult.builder().addTextContent(allRequests).build();
- }));
- }
-}
diff --git
a/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/internal/LogSnapshot.java
b/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/internal/LogSnapshot.java
deleted file mode 100644
index 80dce2f5..00000000
---
a/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/internal/LogSnapshot.java
+++ /dev/null
@@ -1,42 +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.internal;
-
-import java.util.Collections;
-import java.util.Map;
-
-import ch.qos.logback.classic.Level;
-
-/**
- * Stores only the lightweight, stable parts of a log event so the in-memory
buffer
- * does not retain full {@code ILoggingEvent} object graphs.
- */
-public record LogSnapshot(
- long timeMillis,
- Level level,
- String loggerName,
- String threadName,
- String formattedMessage,
- String throwableText,
- Map<String, String> mdc) {
-
- public LogSnapshot {
- mdc = mdc == null ? Collections.emptyMap() :
Collections.unmodifiableMap(mdc);
- }
-}
diff --git
a/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/internal/StructuredLogBuffer.java
b/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/internal/StructuredLogBuffer.java
deleted file mode 100644
index 80c64fe3..00000000
---
a/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/internal/StructuredLogBuffer.java
+++ /dev/null
@@ -1,100 +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.internal;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Deque;
-import java.util.List;
-import java.util.regex.Pattern;
-
-import ch.qos.logback.classic.Level;
-
-public class StructuredLogBuffer {
-
- private final Object lock = new Object();
- private final Deque<LogSnapshot> entries = new ArrayDeque<>();
- private int maxEntriesKept;
-
- public StructuredLogBuffer(int maxEntriesKept) {
- this.maxEntriesKept = Math.max(1, maxEntriesKept);
- }
-
- public void append(LogSnapshot snapshot) {
- synchronized (lock) {
- entries.addLast(snapshot);
- trimToSize();
- }
- }
-
- public List<LogSnapshot> getRecent(Pattern pattern, Level minLevel, int
maxEntries) {
- synchronized (lock) {
- List<LogSnapshot> matches = new ArrayList<>();
- int remaining = Math.max(1, maxEntries);
-
- for (var iterator = entries.descendingIterator();
iterator.hasNext() && remaining > 0; ) {
- LogSnapshot snapshot = iterator.next();
- if (!matches(snapshot, pattern, minLevel)) {
- continue;
- }
- matches.add(snapshot);
- remaining--;
- }
-
- return matches;
- }
- }
-
- private boolean matches(LogSnapshot snapshot, Pattern pattern, Level
minLevel) {
- if (snapshot.level().isGreaterOrEqual(minLevel)) {
- if (pattern == null) {
- return true;
- }
- return matchesField(pattern, snapshot.level() != null ?
snapshot.level().levelStr : null)
- || matchesField(pattern, snapshot.loggerName())
- || matchesField(pattern, snapshot.threadName())
- || matchesField(pattern, snapshot.formattedMessage())
- || matchesField(pattern, snapshot.throwableText())
- || matchesMdc(pattern, snapshot);
- }
- return false;
- }
-
- private boolean matchesMdc(Pattern pattern, LogSnapshot snapshot) {
- if (snapshot.mdc().isEmpty()) {
- return false;
- }
- for (var entry : snapshot.mdc().entrySet()) {
- if (matchesField(pattern, entry.getKey()) || matchesField(pattern,
entry.getValue())) {
- return true;
- }
- }
- return false;
- }
-
- private boolean matchesField(Pattern pattern, String value) {
- return value != null && !value.isEmpty() &&
pattern.matcher(value).find();
- }
-
- private void trimToSize() {
- while (entries.size() > maxEntriesKept) {
- entries.removeFirst();
- }
- }
-}
diff --git
a/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/internal/StructuredLogBufferAppender.java
b/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/internal/StructuredLogBufferAppender.java
deleted file mode 100644
index 95a02db0..00000000
---
a/mcp-server-contributions/src/main/java/org/apache/sling/mcp/server/impl/contribs/internal/StructuredLogBufferAppender.java
+++ /dev/null
@@ -1,173 +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.internal;
-
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import ch.qos.logback.classic.spi.ILoggingEvent;
-import ch.qos.logback.classic.spi.IThrowableProxy;
-import ch.qos.logback.classic.spi.StackTraceElementProxy;
-import ch.qos.logback.core.Appender;
-import ch.qos.logback.core.AppenderBase;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.metatype.annotations.AttributeDefinition;
-import org.osgi.service.metatype.annotations.Designate;
-import org.osgi.service.metatype.annotations.ObjectClassDefinition;
-
-@Component(
- service = {Appender.class, StructuredLogBufferAppender.class},
- property = {
- "loggers=ROOT",
- "service.description=Structured in-memory MCP log appender",
- "service.vendor=The Apache Software Foundation"
- })
-@Designate(ocd = StructuredLogBufferAppender.Configuration.class)
-public class StructuredLogBufferAppender extends AppenderBase<ILoggingEvent> {
-
- // Forward compatibility with logback 1.5+, where IThrowableProxy may
expose getOverridingMessage().
- private static final MethodHandle GET_OVERRIDING_MESSAGE =
findGetOverridingMessage();
-
- @ObjectClassDefinition(name = "Apache Sling MCP Structured Log Buffer")
- public @interface Configuration {
-
- @AttributeDefinition(name = "Max entries")
- int maxEntries() default 10000;
- }
-
- private final StructuredLogBuffer buffer;
-
- @Activate
- public StructuredLogBufferAppender(Configuration configuration) {
- buffer = new StructuredLogBuffer(configuration.maxEntries());
- setName("mcp-structured-log-buffer");
- }
-
- public StructuredLogBuffer getBuffer() {
- return buffer;
- }
-
- @Override
- protected void append(ILoggingEvent eventObject) {
- if (eventObject == null) {
- return;
- }
-
- buffer.append(new LogSnapshot(
- eventObject.getTimeStamp(),
- eventObject.getLevel(),
- eventObject.getLoggerName(),
- eventObject.getThreadName(),
- eventObject.getFormattedMessage(),
- getThrowableText(eventObject),
- copyMdc(eventObject)));
- }
-
- private Map<String, String> copyMdc(ILoggingEvent eventObject) {
- Map<String, String> mdc = eventObject.getMDCPropertyMap();
- if (mdc == null || mdc.isEmpty()) {
- return Map.of();
- }
- return new LinkedHashMap<>(mdc);
- }
-
- private String getThrowableText(ILoggingEvent eventObject) {
- IThrowableProxy throwableProxy = eventObject.getThrowableProxy();
- if (throwableProxy == null) {
- return null;
- }
-
- StringBuilder text = new StringBuilder();
- appendThrowable(text, throwableProxy, null);
- return text.toString();
- }
-
- private void appendThrowable(StringBuilder text, IThrowableProxy
throwableProxy, String prefix) {
- if (prefix != null) {
- text.append(prefix);
- }
- text.append(getThrowableHeader(throwableProxy)).append('\n');
-
- StackTraceElementProxy[] stackTrace =
throwableProxy.getStackTraceElementProxyArray();
- if (stackTrace != null) {
- int framesToRender = Math.max(0, stackTrace.length - Math.max(0,
throwableProxy.getCommonFrames()));
- for (int i = 0; i < framesToRender; i++) {
- text.append('\t').append(stackTrace[i]).append('\n');
- }
- if (throwableProxy.getCommonFrames() > 0) {
- text.append("\t... ")
- .append(throwableProxy.getCommonFrames())
- .append(" common frames omitted")
- .append('\n');
- }
- }
-
- IThrowableProxy[] suppressed = throwableProxy.getSuppressed();
- if (suppressed != null) {
- for (IThrowableProxy suppressedThrowable : suppressed) {
- appendThrowable(text, suppressedThrowable, "Suppressed: ");
- }
- }
-
- IThrowableProxy cause = throwableProxy.getCause();
- if (cause != null) {
- appendThrowable(text, cause, "Caused by: ");
- }
- }
-
- private String getThrowableHeader(IThrowableProxy throwableProxy) {
- String overridingMessage = getOverridingMessage(throwableProxy);
- if (overridingMessage != null && !overridingMessage.isEmpty()) {
- return overridingMessage;
- }
-
- StringBuilder header = new
StringBuilder(throwableProxy.getClassName());
- String message = throwableProxy.getMessage();
- if (message != null && !message.isEmpty()) {
- header.append(": ").append(message);
- }
- return header.toString();
- }
-
- private String getOverridingMessage(IThrowableProxy throwableProxy) {
- if (GET_OVERRIDING_MESSAGE == null) {
- return null;
- }
-
- try {
- Object overridingMessage =
GET_OVERRIDING_MESSAGE.invoke(throwableProxy);
- return overridingMessage instanceof String ? (String)
overridingMessage : null;
- } catch (Throwable e) {
- return null;
- }
- }
-
- private static MethodHandle findGetOverridingMessage() {
- try {
- return MethodHandles.publicLookup()
- .findVirtual(IThrowableProxy.class,
"getOverridingMessage", MethodType.methodType(String.class));
- } catch (NoSuchMethodException | IllegalAccessException e) {
- return null;
- }
- }
-}
diff --git
a/mcp-server-contributions/src/main/resources/SLING-INF/libs/sling/mcp/prompts/new-sling-servlet.md
b/mcp-server-contributions/src/main/resources/SLING-INF/libs/sling/mcp/prompts/new-sling-servlet.md
deleted file mode 100644
index c92cb837..00000000
---
a/mcp-server-contributions/src/main/resources/SLING-INF/libs/sling/mcp/prompts/new-sling-servlet.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: "Create new Sling Servlet"
-description: "Creates a new Sling Servlet in the current project using
annotations"
-argument.resource-type:
- - title: Resource Type
- - required: true
- - description: The Sling resource type to bind this servlet to
----
-
-# Create a new Sling Servlet
-
- Create a new Sling Servlet for resource type: `{resource-type}`
-
- 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}'.
-
\ No newline at end of file
diff --git
a/mcp-server-contributions/src/main/resources/SLING-INF/libs/sling/mcp/prompts/troubleshoot.md
b/mcp-server-contributions/src/main/resources/SLING-INF/libs/sling/mcp/prompts/troubleshoot.md
deleted file mode 100644
index d5e95874..00000000
---
a/mcp-server-contributions/src/main/resources/SLING-INF/libs/sling/mcp/prompts/troubleshoot.md
+++ /dev/null
@@ -1,45 +0,0 @@
----
-title: "Troubleshooting guide"
-description: "Guide to troubleshoot common issues with Apache Sling
applications"
----
-# Troubleshooting Guide
-
-## Overview
-This guide helps you diagnose and resolve common issues with Apache Sling MCP
Server.
-
-## Common Issues
-
-### MCP Server Not Responding
-- Verify the MCP servlet is registered and active
-- Check OSGi component status
-- Review log files for errors
-
-### Bundle Issues
-- Check bundle state (ACTIVE, RESOLVED, INSTALLED)
-- Verify all dependencies are satisfied
-- Use the OSGi diagnostic tools
-
-### Performance Problems
-- Review recent requests and response times
-- Check system resources
-- Analyze thread dumps if available
-
-### Component Registration Issues
-- Verify component configurations
-- Check service dependencies
-- Review component lifecycle logs
-
-## Diagnostic Tools
-The MCP Server provides several diagnostic tools:
-- Bundle state inspection
-- Component resource analysis
-- Recent request tracking
-- Log file access
-- OSGi diagnostic reports
-
-## Getting Help
-If you continue to experience issues:
-1. Gather diagnostic information using the MCP tools
-2. Review the Apache Sling documentation
-3. Check the Apache Sling mailing lists
-4. File an issue in the Apache Sling JIRA
diff --git
a/mcp-server-contributions/src/test/java/org/apache/sling/mcp/server/impl/contribs/internal/StructuredLogBufferAppenderTest.java
b/mcp-server-contributions/src/test/java/org/apache/sling/mcp/server/impl/contribs/internal/StructuredLogBufferAppenderTest.java
deleted file mode 100644
index c9064e72..00000000
---
a/mcp-server-contributions/src/test/java/org/apache/sling/mcp/server/impl/contribs/internal/StructuredLogBufferAppenderTest.java
+++ /dev/null
@@ -1,62 +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.internal;
-
-import java.util.List;
-import java.util.Map;
-
-import ch.qos.logback.classic.Level;
-import ch.qos.logback.classic.Logger;
-import ch.qos.logback.classic.LoggerContext;
-import ch.qos.logback.classic.spi.LoggingEvent;
-import org.junit.jupiter.api.Test;
-import org.osgi.util.converter.Converters;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-
-class StructuredLogBufferAppenderTest {
-
- @Test
- void appenderSnapshotsFormattedMessageAndThrowable() {
- StructuredLogBufferAppender appender = new
StructuredLogBufferAppender(configuration(5));
-
- LoggerContext context = new LoggerContext();
- appender.setContext(context);
- Logger logger = context.getLogger("test.logger");
- RuntimeException failure = new RuntimeException("error");
- LoggingEvent event = new LoggingEvent(getClass().getName(), logger,
Level.ERROR, "message", failure, null);
- event.setMDCPropertyMap(java.util.Map.of());
- event.setThreadName("worker-1");
-
- appender.append(event);
-
- List<LogSnapshot> logs = appender.getBuffer().getRecent(null,
Level.TRACE, 10);
- assertEquals(1, logs.size());
- assertEquals("message", logs.get(0).formattedMessage());
- assertEquals("worker-1", logs.get(0).threadName());
- assertNotNull(logs.get(0).throwableText());
- }
-
- private StructuredLogBufferAppender.Configuration configuration(int
maxEntries) {
- return Converters.standardConverter()
- .convert(Map.of("maxEntries", maxEntries))
- .to(StructuredLogBufferAppender.Configuration.class);
- }
-}
diff --git
a/mcp-server-contributions/src/test/java/org/apache/sling/mcp/server/impl/contribs/internal/StructuredLogBufferTest.java
b/mcp-server-contributions/src/test/java/org/apache/sling/mcp/server/impl/contribs/internal/StructuredLogBufferTest.java
deleted file mode 100644
index d92efced..00000000
---
a/mcp-server-contributions/src/test/java/org/apache/sling/mcp/server/impl/contribs/internal/StructuredLogBufferTest.java
+++ /dev/null
@@ -1,64 +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.internal;
-
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Pattern;
-
-import ch.qos.logback.classic.Level;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-class StructuredLogBufferTest {
-
- @Test
- void keepsOnlyNewestEntriesWithinCapacity() {
- StructuredLogBuffer buffer = new StructuredLogBuffer(2);
-
- buffer.append(snapshot(1L, Level.INFO, "first"));
- buffer.append(snapshot(2L, Level.INFO, "second"));
- buffer.append(snapshot(3L, Level.INFO, "third"));
-
- List<LogSnapshot> logs = buffer.getRecent(null, Level.TRACE, 10);
- assertEquals(
- List.of("third", "second"),
- logs.stream().map(LogSnapshot::formattedMessage).toList());
- }
-
- @Test
- void filtersByLevelAndRegex() {
- StructuredLogBuffer buffer = new StructuredLogBuffer(10);
-
- buffer.append(snapshot(1L, Level.DEBUG, "debug trace"));
- buffer.append(snapshot(2L, Level.INFO, "first user ok"));
- buffer.append(snapshot(3L, Level.ERROR, "first user failure"));
-
- List<LogSnapshot> logs = buffer.getRecent(Pattern.compile("first",
Pattern.CASE_INSENSITIVE), Level.INFO, 10);
-
- assertEquals(
- List.of("first user failure", "first user ok"),
- logs.stream().map(LogSnapshot::formattedMessage).toList());
- }
-
- private LogSnapshot snapshot(long timeMillis, Level level, String message)
{
- return new LogSnapshot(timeMillis, level, "logger", "thread", message,
null, Map.of());
- }
-}