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}