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 9fb2a6649eed9f38cceb15ebbb4605f681b9728d
Author: Robert Munteanu <[email protected]>
AuthorDate: Wed Nov 12 14:30:20 2025 +0100

    Initial commit of a basic MCP server implementation for Sling
---
 README.md                                          |  13 ++
 bnd.bnd                                            |   3 +
 pom.xml                                            | 206 ++++++++++++++++++
 src/main/features/main.json                        |  36 ++++
 .../apache/sling/mcp/server/impl/McpServlet.java   | 230 +++++++++++++++++++++
 5 files changed, 488 insertions(+)

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..599c6a0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+# Apache Sling MCP Server
+
+Experimental MCP Server implementation for Apache Sling.
+
+## Usage
+
+Start up the MCP server, based on the Apache Sling Starter
+
+```
+$ mvn package feature-launcher:start feature-launcher:stop 
-Dfeature-launcher.waitForInput
+```
+
+Then open up your coding assistant tool and add an remote MCP server with 
location http://localhost:8080/mcp .
diff --git a/bnd.bnd b/bnd.bnd
new file mode 100644
index 0000000..27af7f1
--- /dev/null
+++ b/bnd.bnd
@@ -0,0 +1,3 @@
+# workaround for https://github.com/modelcontextprotocol/java-sdk/issues/562
+Private-Package: io.modelcontextprotocol.json.jackson, \
+    io.modelcontextprotocol.json.schema.jackson
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..4a2323c
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,206 @@
+<?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>63</version>
+        <relativePath />
+    </parent>
+
+    <artifactId>org.apache.sling.mcp-server</artifactId>
+    <version>0.1.0-SNAPSHOT</version>
+
+    <name>Apache Sling MCP Server</name>
+
+    <properties>
+        <sling.java.version>17</sling.java.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.framework</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.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>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.modelcontextprotocol.sdk</groupId>
+            <artifactId>mcp</artifactId>
+            <version>0.15.0</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.sling</groupId>
+                <artifactId>slingfeature-maven-plugin</artifactId>
+                <version>1.9.2</version>
+                <extensions>true</extensions>
+                <configuration>
+                    
<skipAddFeatureDependencies>true</skipAddFeatureDependencies>
+                    <framework>
+                        <groupId>org.apache.felix</groupId>
+                        <artifactId>org.apache.felix.framework</artifactId>
+                        <version>7.0.5</version>
+                    </framework>
+
+                    <!--
+                        Prepares a feature model aggregate that takes the 
following features from
+                        the Sling Starter:
+                        - nosample_base - the base Sling Starter
+                        - oak_persistence_sns - SegmentNodeStore persistence 
for Oak
+                        - composum - The Composum Nodes administration tool
+                        Notably missing are the slingshot and starter samples
+                     -->
+                    <aggregates>
+                        <aggregate>
+                            <classifier>app</classifier>
+                            <filesInclude>main.json</filesInclude>
+                            <attach>false</attach>
+                            <includeArtifact>
+                                <groupId>org.apache.sling</groupId>
+                                
<artifactId>org.apache.sling.starter</artifactId>
+                                <classifier>nosample_base</classifier>
+                                <version>14-SNAPSHOT</version>
+                                <type>slingosgifeature</type>
+                            </includeArtifact>
+                            <includeArtifact>
+                                <groupId>org.apache.sling</groupId>
+                                
<artifactId>org.apache.sling.starter</artifactId>
+                                <classifier>oak_persistence_sns</classifier>
+                                <version>14-SNAPSHOT</version>
+                                <type>slingosgifeature</type>
+                            </includeArtifact>
+                            <includeArtifact>
+                                <groupId>org.apache.sling</groupId>
+                                
<artifactId>org.apache.sling.starter</artifactId>
+                                <classifier>starter</classifier>
+                                <version>14-SNAPSHOT</version>
+                                <type>slingosgifeature</type>
+                            </includeArtifact>
+                            <includeArtifact>
+                                <groupId>org.apache.sling</groupId>
+                                
<artifactId>org.apache.sling.starter</artifactId>
+                                <classifier>composum</classifier>
+                                <version>14-SNAPSHOT</version>
+                                <type>slingosgifeature</type>
+                            </includeArtifact>
+                        </aggregate>
+                    </aggregates>
+                    <repositories>
+                        <repository>
+                            <includeClassifier>app</includeClassifier>
+                        </repository>
+                    </repositories>
+                    <scans>
+                        <scan>
+                            <includeClassifier>app</includeClassifier>
+                        </scan>
+                    </scans>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>prepare-features</id>
+                        <goals>
+                            <goal>aggregate-features</goal>
+                            <goal>analyse-features</goal>
+                            <goal>attach-features</goal>
+                        </goals>
+                        <phase>prepare-package</phase>
+                    </execution>
+                    <execution>
+                        <id>create-app-repository</id>
+                        <goals>
+                            <goal>repository</goal>
+                        </goals>
+                        <phase>package</phase>
+                    </execution>
+                </executions>
+            </plugin>
+            <!-- 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>
+            <!-- Added: Feature Launcher plugin for manually starting the 
aggregated 'app' feature -->
+            <plugin>
+                <groupId>org.apache.sling</groupId>
+                <artifactId>feature-launcher-maven-plugin</artifactId>
+                <configuration>
+                    <launches>
+                        <launch>
+                            <id>app</id>
+                            
<featureFile>${project.slingfeature.outputDirectory}/feature-app.json</featureFile>
+                        </launch>
+                    </launches>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/src/main/features/main.json b/src/main/features/main.json
new file mode 100644
index 0000000..c34fb1f
--- /dev/null
+++ b/src/main/features/main.json
@@ -0,0 +1,36 @@
+{
+  "bundles": [
+     {
+        "id": "${project.groupId}:${project.artifactId}:${project.version}",
+        "start-order": 25
+    },
+    {
+        "id": "io.modelcontextprotocol.sdk:mcp-core:0.15.0",
+        "start-order": 25
+    },
+    {
+        "id": "io.projectreactor:reactor-core:3.7.0",
+        "start-order": 25
+    },
+    {
+        "id": "org.reactivestreams:reactive-streams:1.0.4",
+        "start-order": 25
+    },
+    {
+        "id": "com.networknt:json-schema-validator:1.5.7",
+        "start-order": 25
+    },
+    {
+        "id": "com.ethlo.time:itu:1.10.3",
+        "start-order": 25
+    },
+    {
+        "id": 
"com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.3",
+        "start-order": 25
+    },
+    {
+        "id": "org.yaml:snakeyaml:2.3",
+        "start-order": 25
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/mcp/server/impl/McpServlet.java 
b/src/main/java/org/apache/sling/mcp/server/impl/McpServlet.java
new file mode 100644
index 0000000..80078df
--- /dev/null
+++ b/src/main/java/org/apache/sling/mcp/server/impl/McpServlet.java
@@ -0,0 +1,230 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.json.McpJsonMapper;
+import io.modelcontextprotocol.json.jackson.JacksonMcpJsonMapper;
+import io.modelcontextprotocol.json.schema.jackson.DefaultJsonSchemaValidator;
+import io.modelcontextprotocol.server.McpServer;
+import io.modelcontextprotocol.server.McpStatelessServerFeatures;
+import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncPromptSpecification;
+import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncToolSpecification;
+import io.modelcontextprotocol.server.McpStatelessSyncServer;
+import 
io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport;
+import io.modelcontextprotocol.spec.McpSchema;
+import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
+import io.modelcontextprotocol.spec.McpSchema.Prompt;
+import io.modelcontextprotocol.spec.McpSchema.PromptArgument;
+import io.modelcontextprotocol.spec.McpSchema.PromptMessage;
+import io.modelcontextprotocol.spec.McpSchema.ReadResourceResult;
+import io.modelcontextprotocol.spec.McpSchema.Resource;
+import io.modelcontextprotocol.spec.McpSchema.ResourceContents;
+import io.modelcontextprotocol.spec.McpSchema.ResourceTemplate;
+import io.modelcontextprotocol.spec.McpSchema.Role;
+import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
+import io.modelcontextprotocol.spec.McpSchema.TextResourceContents;
+import io.modelcontextprotocol.spec.McpSchema.Tool;
+import jakarta.servlet.Servlet;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.sling.api.SlingJakartaHttpServletRequest;
+import org.apache.sling.api.SlingJakartaHttpServletResponse;
+import org.apache.sling.api.servlets.SlingJakartaAllMethodsServlet;
+import org.apache.sling.servlets.annotations.SlingServletPaths;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.wiring.FrameworkWiring;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+
+@Component(service = Servlet.class)
+@SlingServletPaths(value = {McpServlet.ENDPOINT})
+public class McpServlet extends SlingJakartaAllMethodsServlet {
+
+    static final String ENDPOINT = "/mcp";
+    private static final long serialVersionUID = 1L;
+    private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
+
+    private McpStatelessSyncServer syncServer;
+    private HttpServletStatelessServerTransport transportProvider;
+    private MethodHandle doGetMethod;
+    private MethodHandle doPostMethod;
+
+    @Activate
+    public McpServlet(BundleContext ctx) throws IllegalAccessException, 
NoSuchMethodException {
+
+        McpJsonMapper jsonMapper = new JacksonMcpJsonMapper(new 
ObjectMapper());
+
+        transportProvider = HttpServletStatelessServerTransport.builder()
+                .messageEndpoint(ENDPOINT)
+                .jsonMapper(jsonMapper)
+                .build();
+
+        MethodHandles.Lookup privateLookup =
+                
MethodHandles.privateLookupIn(HttpServletStatelessServerTransport.class, 
LOOKUP);
+
+        doGetMethod = privateLookup.findVirtual(
+                HttpServletStatelessServerTransport.class,
+                "doGet",
+                java.lang.invoke.MethodType.methodType(
+                        void.class, HttpServletRequest.class, 
HttpServletResponse.class));
+        doPostMethod = privateLookup.findVirtual(
+                HttpServletStatelessServerTransport.class,
+                "doPost",
+                java.lang.invoke.MethodType.methodType(
+                        void.class, HttpServletRequest.class, 
HttpServletResponse.class));
+
+        syncServer = McpServer.sync(transportProvider)
+                .serverInfo("apache-sling", "0.1.0")
+                .jsonMapper(jsonMapper)
+                .jsonSchemaValidator(new DefaultJsonSchemaValidator())
+                .capabilities(ServerCapabilities.builder()
+                        .tools(true)
+                        .prompts(true)
+                        .resources(true, true)
+                        .build())
+                .build();
+
+        var schema = """
+                {
+                  "type" : "object",
+                  "id" : "urn:jsonschema:Operation",
+                  "properties" : { }
+                }
+                """;
+        var syncToolSpecification = new SyncToolSpecification(
+                Tool.builder()
+                        .name("refresh-packages")
+                        .description("Refresh Packages")
+                        .inputSchema(jsonMapper, schema)
+                        .build(),
+                (exchange, arguments) -> {
+                    FrameworkWiring fw = 
ctx.getBundle(0).adapt(FrameworkWiring.class);
+
+                    fw.refreshBundles(null);
+
+                    return new CallToolResult("Bundles refreshed 
successfully", Boolean.FALSE);
+                });
+
+        // Register tools, resources, and prompts
+        syncServer.addTool(syncToolSpecification);
+        syncServer.addPrompt(new SyncPromptSpecification(
+                new Prompt(
+                        "new-sling-servlet",
+                        "Create new Sling Servlet",
+                        "Creates a new Sling Servlet in the current project 
using annotations",
+                        List.of(new PromptArgument(
+                                "resource-type",
+                                "Resource type",
+                                "The Sling resource type to bind this servlet 
to.",
+                                true))),
+                (context, request) -> {
+                    String resourceType = (String) 
request.arguments().get("resource-type");
+                    PromptMessage msg = new PromptMessage(
+                            Role.ASSISTANT,
+                            new McpSchema.TextContent(
+                                    "Create a new Sling Servlet for resource 
type: " + resourceType
+                                            + " . Use the Sling-specific OSGi 
declarative services annotations - @SlingServletResourceTypes and @Component . 
Configure by default with the GET method and the json extension. Provide a 
basic implementation of the doGet method that returns a JSON response with a 
message 'Hello from Sling Servlet at resource type <resource-type>'."));
+                    return new McpSchema.GetPromptResult("Result of creation", 
List.of(msg));
+                }));
+        syncServer.addResource(new 
McpStatelessServerFeatures.SyncResourceSpecification(
+                new Resource.Builder()
+                        .name("bundle")
+                        .uri("bundle://")
+                        .description("OSGi bundle status")
+                        .mimeType("text/plain")
+                        .build(),
+                (context, request) -> {
+                    List<McpSchema.ResourceContents> res = 
Stream.of(ctx.getBundles())
+                            .map(b -> new TextResourceContents(
+                                    "bundle://" + b.getSymbolicName(),
+                                    "text-plain",
+                                    "Bundle " + b.getSymbolicName() + " is in 
state " + b.getState()))
+                            .collect(Collectors.toList());
+
+                    return new McpSchema.ReadResourceResult(res);
+                }));
+
+        syncServer.addResourceTemplate(new 
McpStatelessServerFeatures.SyncResourceTemplateSpecification(
+                new ResourceTemplate.Builder()
+                        .uriTemplate("bundles://state/{state}")
+                        .name("bundles")
+                        .build(),
+                (context, request) -> {
+                    List<ResourceContents> bundles = List.of();
+                    if 
("bundles://state/resolved".equals(request.uri().toLowerCase(Locale.ENGLISH))) {
+                        bundles = Arrays.stream(ctx.getBundles())
+                                .filter(b -> b.getState() == Bundle.RESOLVED)
+                                .<ResourceContents>map(b -> new 
TextResourceContents(
+                                        "bundle://" + b.getSymbolicName(),
+                                        "text-plain",
+                                        "Bundle " + b.getSymbolicName() + " is 
in state " + b.getState()))
+                                .toList();
+                    }
+
+                    return new ReadResourceResult(bundles);
+                }));
+    }
+
+    @Override
+    protected void doGet(
+            @NotNull SlingJakartaHttpServletRequest request, @NotNull 
SlingJakartaHttpServletResponse response)
+            throws ServletException, IOException {
+        try {
+            doGetMethod.invoke(transportProvider, request, response);
+        } catch (ServletException | IOException | RuntimeException | Error e) {
+            throw e;
+        } catch (Throwable t) {
+            throw new ServletException(t);
+        }
+    }
+
+    @Override
+    protected void doPost(
+            @NotNull SlingJakartaHttpServletRequest request, @NotNull 
SlingJakartaHttpServletResponse response)
+            throws ServletException, IOException {
+        try {
+            doPostMethod.invoke(transportProvider, request, response);
+        } catch (ServletException | IOException | RuntimeException | Error e) {
+            throw e;
+        } catch (Throwable t) {
+            throw new ServletException(t);
+        }
+    }
+
+    @Deactivate
+    public void close() {
+        if (syncServer != null) {
+            syncServer.close();
+        }
+    }
+}

Reply via email to