CtrlK
CommunityDocumentationLog inGet started
Tessl Logo

tessl/maven-org-springframework-ai--spring-ai-starter-mcp-server

Spring Boot Starter for building Model Context Protocol (MCP) servers with auto-configuration, annotation-based tool/resource/prompt definitions, and support for STDIO, SSE, and Streamable-HTTP transports

Overview
Eval results
Files

native-image-support.mddocs/reference/

Native Image Support

GraalVM native image compilation support for Spring AI MCP Server applications, enabling ahead-of-time (AOT) compilation for fast startup and reduced memory footprint.

Package Information

Native image support is provided automatically through Spring Boot's AOT processing infrastructure. The starter includes runtime hints for all MCP schema classes to ensure proper reflection support in native images.

Capabilities

McpHints

Runtime hints registrar that registers MCP schema classes for reflection in GraalVM native images.

/**
 * Runtime hints registrar for MCP schema types
 * Location: org.springframework.ai.mcp.aot.McpHints
 * Implements: org.springframework.aot.hint.RuntimeHintsRegistrar
 */
class McpHints implements RuntimeHintsRegistrar {
    /**
     * Register runtime hints for MCP classes
     * @param hints Runtime hints registry
     * @param classLoader Class loader
     */
    @Override
    void registerHints(RuntimeHints hints, ClassLoader classLoader);
}

Registered Classes:

The McpHints registrar automatically registers all MCP schema classes from io.modelcontextprotocol.spec.McpSchema for reflection, including:

  • Tool and tool-related types
  • Resource types
  • Prompt types
  • Completion types
  • Request/response types
  • Content types (text, image, embedded resource)
  • Notification types (logging, progress, message)
  • All nested records and enums

Usage:

The hints are automatically applied when building a native image with Spring Boot 3.x and GraalVM. No manual configuration is required.

Building Native Images

Prerequisites

# Install GraalVM
sdk install java 21.0.1-graalce

# Use GraalVM
sdk use java 21.0.1-graalce

Maven Build

<!-- pom.xml -->
<build>
    <plugins>
        <plugin>
            <groupId>org.graalvm.buildtools</groupId>
            <artifactId>native-maven-plugin</artifactId>
        </plugin>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

Build native image:

mvn -Pnative native:compile

Gradle Build

// build.gradle
plugins {
    id 'org.springframework.boot' version '3.2.0'
    id 'org.graalvm.buildtools.native' version '0.9.28'
}

Build native image:

gradle nativeCompile

Native Image Configuration

Application Properties

# application.properties for native builds

# Server configuration
spring.ai.mcp.server.enabled=true
spring.ai.mcp.server.stdio=true
spring.ai.mcp.server.name=native-mcp-server
spring.ai.mcp.server.version=1.0.0

# Reduce startup overhead
spring.main.lazy-initialization=false

Custom Reflection Hints

If you have custom types used in MCP tools, add reflection hints:

import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportRuntimeHints;

@Configuration
@ImportRuntimeHints(CustomMcpHints.class)
public class NativeConfig {
}

class CustomMcpHints implements RuntimeHintsRegistrar {
    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        // Register custom types used in @McpTool methods
        hints.reflection()
            .registerType(MyCustomRequest.class)
            .registerType(MyCustomResponse.class);
    }
}

Example Native Image Application

Simple Calculator Server

@SpringBootApplication
@ImportRuntimeHints(McpHints.class)
public class CalculatorServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(CalculatorServerApplication.class, args);
    }
}

@Component
class CalculatorTools {

    @McpTool(name = "add", description = "Add two numbers")
    public double add(
            @McpToolParam(description = "First number", required = true) double a,
            @McpToolParam(description = "Second number", required = true) double b) {
        return a + b;
    }

    @McpTool(name = "multiply", description = "Multiply two numbers")
    public double multiply(
            @McpToolParam(description = "First number", required = true) double a,
            @McpToolParam(description = "Second number", required = true) double b) {
        return a * b;
    }
}

Building and Running

# Build native image
mvn -Pnative native:compile

# Run native executable
./target/calculator-server

# Performance comparison
echo "Native startup time:"
time ./target/calculator-server --server.port=0 &
NATIVE_PID=$!
sleep 2
kill $NATIVE_PID

echo "JVM startup time:"
time java -jar target/calculator-server.jar --server.port=0 &
JVM_PID=$!
sleep 2
kill $JVM_PID

Native Image Benefits

Fast Startup

Native images start in milliseconds instead of seconds:

  • JVM startup: 2-5 seconds
  • Native image startup: 50-200 milliseconds

Reduced Memory Footprint

Native images consume less memory:

  • JVM RSS: 150-300 MB
  • Native image RSS: 40-80 MB

Instant Peak Performance

No warmup required - native images run at peak performance immediately.

Limitations and Considerations

Reflection Requirements

All classes used in MCP operations must be registered for reflection:

  • Automatically registered: MCP schema types (via McpHints)
  • Manually register: Custom request/response types, DTOs used in tools

Dynamic Class Loading

Avoid dynamic class loading in tool implementations:

// Bad - will fail in native image
@McpTool(name = "dynamic")
public String dynamicTool(String className) throws Exception {
    Class<?> clazz = Class.forName(className); // Fails!
    return clazz.getName();
}

// Good - use statically known types
@McpTool(name = "process")
public String processTool(String type) {
    return switch(type) {
        case "text" -> processText();
        case "json" -> processJson();
        default -> "Unknown type";
    };
}

Serialization

Jackson serialization works automatically for registered types. Ensure custom types are properly annotated:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class CustomType {
    private final String value;

    @JsonCreator
    public CustomType(@JsonProperty("value") String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

Testing Native Images

Native Tests

Run tests in native image mode:

# Maven
mvn -PnativeTest test

# Gradle
gradle nativeTest

Native Build Tests

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.junit.jupiter.api.Test;

@SpringBootTest(webEnvironment = WebEnvironment.NONE)
class NativeImageTests {

    @Autowired
    private ApplicationContext context;

    @Test
    void contextLoads() {
        // Verify MCP server beans are available
        assertNotNull(context.getBean("mcpServerAutoConfiguration"));
    }

    @Test
    void mcpToolsRegistered() {
        // Verify tools are registered
        List<SyncToolSpecification> tools = context.getBeansOfType(
            SyncToolSpecification.class
        ).values().stream().toList();

        assertFalse(tools.isEmpty());
    }
}

Troubleshooting

Missing Reflection Configuration

If you encounter ClassNotFoundException or reflection errors:

  1. Check build logs for missing reflection hints
  2. Add custom RuntimeHintsRegistrar for your types
  3. Use @RegisterReflectionForBinding for known types
@Configuration
@RegisterReflectionForBinding({MyType.class, AnotherType.class})
public class ReflectionConfig {
}

Serialization Issues

For Jackson serialization issues in native images:

@Configuration
public class JacksonConfig {

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer nativeImageCustomizer() {
        return builder -> {
            builder.modules(new ParameterNamesModule());
            builder.visibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        };
    }
}

Core Imports

import org.springframework.ai.mcp.aot.McpHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.nativex.hint.TypeHint;

Resources

  • Spring Boot Native Image Support
  • GraalVM Native Image
  • Spring AOT Processing
tessl i tessl/maven-org-springframework-ai--spring-ai-starter-mcp-server@1.1.0

docs

index.md

tile.json