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)); + })); + } +}
