001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.examples.mcp.spring;
018
019import org.apache.juneau.marshall.json.*;
020import org.apache.juneau.marshall.sse.*;
021import org.apache.juneau.rest.server.*;
022import org.apache.juneau.rest.server.mcp.*;
023import org.apache.juneau.rest.server.mcp.v20260728.*;
024
025/**
026 * The Spring Boot flavor of the MCP server.
027 *
028 * <p>
029 * Extending {@link SpringMcpRestServlet} (rather than the plain {@code McpRestServlet}) is the whole point:
030 * it bridges the Spring {@code ApplicationContext} into each MCP handler's per-request {@link org.apache.juneau.commons.inject.BeanStore},
031 * so {@code ctx.getBean(GreetingService.class)} resolves the real Spring-managed {@link GreetingService}
032 * singleton. The same handler on a plain {@code McpRestServlet} would not see Spring beans.
033 *
034 * <p>
035 * When registered at servlet mapping {@code /*} (see {@link SpringExampleApplication}), the MCP endpoint is
036 * served at {@code /mcp} (the Spring convenience servlet mounts its operation at {@code /mcp} relative to the
037 * servlet root).
038 *
039 * @serial exclude
040 */
041@Rest(serializers = {JsonSerializer.class, SseSerializer.class}, parsers = JsonParser.class, defaultAccept = "application/json")
042@SuppressWarnings({
043   "java:S110" // Inheritance depth is inherent to extending the Juneau REST server hierarchy.
044})
045public class SpringExampleMcpServer extends SpringMcpRestServlet {
046
047   private static final long serialVersionUID = 1L;
048
049   @Override
050   public McpServerConfig getMcpConfig() {
051      return new McpServerConfig()
052         .setName("juneau-spring-greeting-example")
053         .setVersion("1.0.0")
054         .addTool(McpToolHandler.of(
055            new McpToolSpec().setName("greet").setDescription("Greets a name using a Spring-injected GreetingService."),
056            (arguments, ctx) -> {
057               var name = String.valueOf(arguments.getOrDefault("name", "world"));
058               return McpToolOutcome.text(ctx.getBean(GreetingService.class)
059                  .map(service -> service.greet(name))
060                  .orElse("(GreetingService was not resolvable from the BeanStore)"));
061            }));
062   }
063
064   /**
065    * On revision {@code 2026-07-28}, {@code server/discover}'s {@code instructions} field is sourced from
066    * {@link McpOptions}, <b>not</b> {@link McpServerConfig#setInstructions}, which only feeds the legacy v1
067    * {@code initialize} handshake.
068    */
069   @Override
070   public McpOptions getMcpOptions() {
071      return new McpOptions()
072         .setInstructions("Call the 'greet' tool; its greeting is produced by a Spring-managed bean.");
073   }
074}