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 ef902bbb5ee86096b6ea394e599e3d23ef643e2a
Author: Robert Munteanu <[email protected]>
AuthorDate: Tue Dec 9 16:39:25 2025 +0100

    feat(mcp-server): split contributions into seperate files
    
    The API is bundle-internal, incomplete and sub-optimal, but it makes it 
possible to use separate
    files right now.
---
 .../mcp/server/impl/McpJsonMapperProvider.java     |  47 ++++++
 .../mcp/server/impl/McpServerContribution.java     |  50 +++++++
 .../apache/sling/mcp/server/impl/McpServlet.java   | 161 +++++----------------
 .../impl/contribs/BundleResourceContribution.java  | 108 ++++++++++++++
 .../impl/contribs/RefreshPackagesContribution.java |  72 +++++++++
 .../impl/contribs/ServletPromptContribution.java   |  71 +++++++++
 6 files changed, 385 insertions(+), 124 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/mcp/server/impl/McpJsonMapperProvider.java 
b/src/main/java/org/apache/sling/mcp/server/impl/McpJsonMapperProvider.java
new file mode 100644
index 0000000..74bece1
--- /dev/null
+++ b/src/main/java/org/apache/sling/mcp/server/impl/McpJsonMapperProvider.java
@@ -0,0 +1,47 @@
+/*
+ * 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 com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.json.McpJsonMapper;
+import io.modelcontextprotocol.json.jackson.JacksonMcpJsonMapper;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+
+@Component
+public class McpJsonMapperProvider {
+
+    private ServiceRegistration<McpJsonMapper> serviceRegistration;
+
+    @Activate
+    public void activate(BundleContext bundleContext) {
+        McpJsonMapper jsonMapper = new JacksonMcpJsonMapper(new 
ObjectMapper());
+        serviceRegistration = 
bundleContext.registerService(McpJsonMapper.class, jsonMapper, null);
+    }
+
+    @Deactivate
+    public void deactivate() {
+        if (serviceRegistration != null) {
+            serviceRegistration.unregister();
+        }
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/mcp/server/impl/McpServerContribution.java 
b/src/main/java/org/apache/sling/mcp/server/impl/McpServerContribution.java
new file mode 100644
index 0000000..4e3168d
--- /dev/null
+++ b/src/main/java/org/apache/sling/mcp/server/impl/McpServerContribution.java
@@ -0,0 +1,50 @@
+/*
+ * 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.util.Optional;
+
+import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncCompletionSpecification;
+import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncPromptSpecification;
+import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncResourceSpecification;
+import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncResourceTemplateSpecification;
+import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncToolSpecification;
+
+public interface McpServerContribution {
+
+    default Optional<SyncToolSpecification> getSyncToolSpecification() {
+        return Optional.empty();
+    }
+
+    default Optional<SyncResourceSpecification> getSyncResourceSpecification() 
{
+        return Optional.empty();
+    }
+
+    default Optional<SyncResourceTemplateSpecification> 
getSyncResourceTemplateSpecification() {
+        return Optional.empty();
+    }
+
+    default Optional<SyncPromptSpecification> getSyncPromptSpecification() {
+        return Optional.empty();
+    }
+
+    default Optional<SyncCompletionSpecification> 
getSyncCompletionSpecification() {
+        return Optional.empty();
+    }
+}
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
index b12faa6..daf9c57 100644
--- a/src/main/java/org/apache/sling/mcp/server/impl/McpServlet.java
+++ b/src/main/java/org/apache/sling/mcp/server/impl/McpServlet.java
@@ -21,36 +21,15 @@ 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 java.util.Optional;
 
-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.SyncCompletionSpecification;
-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.CompleteResult.CompleteCompletion;
-import io.modelcontextprotocol.spec.McpSchema.Prompt;
-import io.modelcontextprotocol.spec.McpSchema.PromptArgument;
-import io.modelcontextprotocol.spec.McpSchema.PromptMessage;
-import io.modelcontextprotocol.spec.McpSchema.ReadResourceResult;
-import io.modelcontextprotocol.spec.McpSchema.Resource;
-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;
@@ -60,16 +39,18 @@ 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;
+import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.metatype.annotations.AttributeDefinition;
 import org.osgi.service.metatype.annotations.Designate;
 import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 
+import static 
org.osgi.service.component.annotations.ReferenceCardinality.MULTIPLE;
+import static 
org.osgi.service.component.annotations.ReferencePolicyOption.GREEDY;
+
 @Component(service = Servlet.class)
 @SlingServletPaths(value = {McpServlet.ENDPOINT})
 @Designate(ocd = McpServlet.Config.class)
@@ -99,22 +80,13 @@ public class McpServlet extends 
SlingJakartaAllMethodsServlet {
     private MethodHandle doGetMethod;
     private MethodHandle doPostMethod;
 
-    private static String getStateString(int state) {
-        return switch (state) {
-            case Bundle.UNINSTALLED -> "UNINSTALLED";
-            case Bundle.INSTALLED -> "INSTALLED";
-            case Bundle.RESOLVED -> "RESOLVED";
-            case Bundle.STARTING -> "STARTING";
-            case Bundle.STOPPING -> "STOPPING";
-            case Bundle.ACTIVE -> "ACTIVE";
-            default -> "UNKNOWN";
-        };
-    }
-
     @Activate
-    public McpServlet(BundleContext ctx, Config config) throws 
IllegalAccessException, NoSuchMethodException {
-
-        McpJsonMapper jsonMapper = new JacksonMcpJsonMapper(new 
ObjectMapper());
+    public McpServlet(
+            BundleContext ctx,
+            Config config,
+            @Reference McpJsonMapper jsonMapper,
+            @Reference(cardinality = MULTIPLE, policyOption = GREEDY) 
List<McpServerContribution> contributions)
+            throws IllegalAccessException, NoSuchMethodException {
 
         transportProvider = HttpServletStatelessServerTransport.builder()
                 .messageEndpoint(ENDPOINT)
@@ -135,21 +107,22 @@ public class McpServlet extends 
SlingJakartaAllMethodsServlet {
                 java.lang.invoke.MethodType.methodType(
                         void.class, HttpServletRequest.class, 
HttpServletResponse.class));
 
-        SyncCompletionSpecification servletCompletionSpec = new 
McpStatelessServerFeatures.SyncCompletionSpecification(
-                new McpSchema.PromptReference("ref/prompt", 
"new-sling-servlet"), (context, request) -> {
-                    return new McpSchema.CompleteResult(new 
CompleteCompletion(List.of(), 0, false));
-                });
-
         String serverVersion = config.serverVersion();
         if (serverVersion == null || serverVersion.isEmpty()) {
             serverVersion = ctx.getBundle().getVersion().toString();
         }
+
+        var completions = contributions.stream()
+                .map(McpServerContribution::getSyncCompletionSpecification)
+                .flatMap(Optional::stream)
+                .toList();
+
         syncServer = McpServer.sync(transportProvider)
                 .serverInfo(config.serverTitle(), serverVersion)
                 .jsonMapper(jsonMapper)
                 .jsonSchemaValidator(new DefaultJsonSchemaValidator())
                 .instructions(config.instructions())
-                .completions(List.of(servletCompletionSpec))
+                .completions(completions)
                 .capabilities(ServerCapabilities.builder()
                         .tools(false)
                         .prompts(false)
@@ -158,85 +131,25 @@ public class McpServlet extends 
SlingJakartaAllMethodsServlet {
                         .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) -> {
-                    String bundleInfo = Stream.of(ctx.getBundles())
-                            .map(b -> "Bundle " + b.getSymbolicName() + " is 
in state " + getStateString(b.getState())
-                                    + " (" + b.getState() + ")")
-                            .collect(Collectors.joining("\n"));
-
-                    TextResourceContents contents = new 
TextResourceContents("bundle://", "text/plain", bundleInfo);
-
-                    return new McpSchema.ReadResourceResult(List.of(contents));
-                }));
-
-        syncServer.addResourceTemplate(new 
McpStatelessServerFeatures.SyncResourceTemplateSpecification(
-                new ResourceTemplate.Builder()
-                        .uriTemplate("bundles://state/{state}")
-                        .name("bundles")
-                        .build(),
-                (context, request) -> {
-                    String bundleInfo = "";
-                    if 
("bundles://state/resolved".equals(request.uri().toLowerCase(Locale.ENGLISH))) {
-                        bundleInfo = Arrays.stream(ctx.getBundles())
-                                .filter(b -> b.getState() == Bundle.RESOLVED)
-                                .map(b -> "Bundle " + b.getSymbolicName() + " 
is in state "
-                                        + getStateString(b.getState()) + " (" 
+ b.getState() + ")")
-                                .collect(Collectors.joining("\n"));
-                    }
-
-                    TextResourceContents contents = new 
TextResourceContents(request.uri(), "text/plain", bundleInfo);
-
-                    return new ReadResourceResult(List.of(contents));
-                }));
+        contributions.stream()
+                .map(McpServerContribution::getSyncToolSpecification)
+                .flatMap(Optional::stream)
+                .forEach(syncTool -> syncServer.addTool(syncTool));
+
+        contributions.stream()
+                .map(McpServerContribution::getSyncResourceSpecification)
+                .flatMap(Optional::stream)
+                .forEach(syncResource -> syncServer.addResource(syncResource));
+
+        contributions.stream()
+                
.map(McpServerContribution::getSyncResourceTemplateSpecification)
+                .flatMap(Optional::stream)
+                .forEach(syncResource -> 
syncServer.addResourceTemplate(syncResource));
+
+        contributions.stream()
+                .map(McpServerContribution::getSyncPromptSpecification)
+                .flatMap(Optional::stream)
+                .forEach(syncPrompt -> syncServer.addPrompt(syncPrompt));
     }
 
     @Override
diff --git 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/BundleResourceContribution.java
 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/BundleResourceContribution.java
new file mode 100644
index 0000000..605eeb3
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/BundleResourceContribution.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.mcp.server.impl.contribs;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import io.modelcontextprotocol.server.McpStatelessServerFeatures;
+import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.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.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+
+@Component
+public class BundleResourceContribution implements McpServerContribution {
+
+    private static String getStateString(int state) {
+        return switch (state) {
+            case Bundle.UNINSTALLED -> "UNINSTALLED";
+            case Bundle.INSTALLED -> "INSTALLED";
+            case Bundle.RESOLVED -> "RESOLVED";
+            case Bundle.STARTING -> "STARTING";
+            case Bundle.STOPPING -> "STOPPING";
+            case Bundle.ACTIVE -> "ACTIVE";
+            default -> "UNKNOWN";
+        };
+    }
+
+    private BundleContext ctx;
+
+    @Activate
+    public BundleResourceContribution(BundleContext ctx) {
+        this.ctx = ctx;
+    }
+
+    @Override
+    public Optional<SyncResourceSpecification> getSyncResourceSpecification() {
+
+        return Optional.of(new 
McpStatelessServerFeatures.SyncResourceSpecification(
+                new Resource.Builder()
+                        .name("bundle")
+                        .uri("bundle://")
+                        .description("OSGi bundle status")
+                        .mimeType("text/plain")
+                        .build(),
+                (context, request) -> {
+                    String bundleInfo = Stream.of(ctx.getBundles())
+                            .map(b -> "Bundle " + b.getSymbolicName() + " is 
in state " + getStateString(b.getState())
+                                    + " (" + b.getState() + ")")
+                            .collect(Collectors.joining("\n"));
+
+                    TextResourceContents contents = new 
TextResourceContents("bundle://", "text/plain", bundleInfo);
+
+                    return new McpSchema.ReadResourceResult(List.of(contents));
+                }));
+    }
+
+    @Override
+    public Optional<SyncResourceTemplateSpecification> 
getSyncResourceTemplateSpecification() {
+        return Optional.of(new 
McpStatelessServerFeatures.SyncResourceTemplateSpecification(
+                new ResourceTemplate.Builder()
+                        .uriTemplate("bundles://state/{state}")
+                        .name("bundles")
+                        .build(),
+                (context, request) -> {
+                    String bundleInfo = "";
+                    if 
("bundles://state/resolved".equals(request.uri().toLowerCase(Locale.ENGLISH))) {
+                        bundleInfo = Arrays.stream(ctx.getBundles())
+                                .filter(b -> b.getState() == Bundle.RESOLVED)
+                                .map(b -> "Bundle " + b.getSymbolicName() + " 
is in state "
+                                        + getStateString(b.getState()) + " (" 
+ b.getState() + ")")
+                                .collect(Collectors.joining("\n"));
+                    }
+
+                    TextResourceContents contents = new 
TextResourceContents(request.uri(), "text/plain", bundleInfo);
+
+                    return new ReadResourceResult(List.of(contents));
+                }));
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/RefreshPackagesContribution.java
 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/RefreshPackagesContribution.java
new file mode 100644
index 0000000..0b02dcd
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/RefreshPackagesContribution.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.mcp.server.impl.contribs;
+
+import java.util.Optional;
+
+import io.modelcontextprotocol.json.McpJsonMapper;
+import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncToolSpecification;
+import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
+import io.modelcontextprotocol.spec.McpSchema.Tool;
+import org.apache.sling.mcp.server.impl.McpServerContribution;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.wiring.FrameworkWiring;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+@Component
+public class RefreshPackagesContribution implements McpServerContribution {
+
+    @Reference
+    private McpJsonMapper jsonMapper;
+
+    private final BundleContext ctx;
+
+    @Activate
+    public RefreshPackagesContribution(BundleContext ctx) {
+        this.ctx = ctx;
+    }
+
+    @Override
+    public Optional<SyncToolSpecification> getSyncToolSpecification() {
+
+        var schema = """
+                {
+                  "type" : "object",
+                  "id" : "urn:jsonschema:Operation",
+                  "properties" : { }
+                }
+                """;
+
+        return Optional.of(new SyncToolSpecification(
+                Tool.builder()
+                        .name("refresh-packages")
+                        .description("Refresh Packages")
+                        .inputSchema(jsonMapper, schema)
+                        .build(),
+                (exchange, arguments) -> {
+                    FrameworkWiring fw = 
ctx.getBundle(0).adapt(FrameworkWiring.class);
+
+                    fw.refreshBundles(null);
+
+                    return new CallToolResult("Bundles refreshed 
successfully", Boolean.FALSE);
+                }));
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/mcp/server/impl/contribs/ServletPromptContribution.java
 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/ServletPromptContribution.java
new file mode 100644
index 0000000..3e67666
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/mcp/server/impl/contribs/ServletPromptContribution.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.mcp.server.impl.contribs;
+
+import java.util.List;
+import java.util.Optional;
+
+import io.modelcontextprotocol.server.McpStatelessServerFeatures;
+import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncCompletionSpecification;
+import 
io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncPromptSpecification;
+import io.modelcontextprotocol.spec.McpSchema;
+import 
io.modelcontextprotocol.spec.McpSchema.CompleteResult.CompleteCompletion;
+import io.modelcontextprotocol.spec.McpSchema.Prompt;
+import io.modelcontextprotocol.spec.McpSchema.PromptArgument;
+import io.modelcontextprotocol.spec.McpSchema.PromptMessage;
+import io.modelcontextprotocol.spec.McpSchema.Role;
+import org.apache.sling.mcp.server.impl.McpServerContribution;
+import org.osgi.service.component.annotations.Component;
+
+@Component
+public class ServletPromptContribution implements McpServerContribution {
+
+    @Override
+    public Optional<SyncPromptSpecification> getSyncPromptSpecification() {
+        return Optional.of(new SyncPromptSpecification(
+                new Prompt(
+                        "new-sling-servlet",
+                        "Create new Sling Servlet",
+                        "Creates a new Sling Servlet in the current project 
using annotations",
+                        List.of(new PromptArgument(
+                                "resource-type",
+                                "Resource type",
+                                "The Sling resource type to bind this servlet 
to.",
+                                true))),
+                (context, request) -> {
+                    String resourceType = (String) 
request.arguments().get("resource-type");
+                    PromptMessage msg = new PromptMessage(
+                            Role.ASSISTANT,
+                            new McpSchema.TextContent(
+                                    "Create a new Sling Servlet for resource 
type: "
+                                            + resourceType
+                                            + " . Use the Sling-specific OSGi 
declarative services annotations - @SlingServletResourceTypes and @Component . 
Configure by default with the GET method and the json extension. Provide a 
basic implementation of the doGet method that returns a JSON response with a 
message 'Hello from Sling Servlet at resource type <resource-type>'."));
+                    return new McpSchema.GetPromptResult("Result of creation", 
List.of(msg));
+                }));
+    }
+
+    @Override
+    public Optional<SyncCompletionSpecification> 
getSyncCompletionSpecification() {
+        // supply no completions for various resource types because it's 
supposed to be specified by the user
+        return Optional.of(new 
McpStatelessServerFeatures.SyncCompletionSpecification(
+                new McpSchema.PromptReference("ref/prompt", 
"new-sling-servlet"), (context, request) -> {
+                    return new McpSchema.CompleteResult(new 
CompleteCompletion(List.of(), 0, false));
+                }));
+    }
+}

Reply via email to