ScreenshotNeo

BlogEngineering

MCP Server in Java: Example

Build an MCP server in Java with Spring AI, choose the right transport, and expose tools safely with runnable code and troubleshooting guidance.

By the ScreenshotNeo team1 October 20268 min read

Yes, you can build an MCP server in Java with either the framework-agnostic MCP Java SDK or Spring AI. For a small Spring application, define a service method with @McpTool, describe its parameters with @McpToolParam, add the matching Spring AI MCP server starter, and select a transport such as Streamable HTTP, SSE, or STDIO.

This guide builds a minimal server, explains each transport, shows dependency choices, and covers production concerns such as state, concurrency, failures, and protocol compatibility.

What an MCP server does

The Model Context Protocol standardizes how AI applications discover and call external capabilities. An MCP server can expose tools, resources, prompt templates, completions, logging, and protocol operations. Clients negotiate protocol versions and capabilities before using those features.

In Java, you can implement the protocol directly with the MCP Java SDK or let Spring AI map annotated services to an MCP server endpoint. The Spring approach is usually the shortest path when your application already uses Spring Boot.

Minimal Spring AI MCP server

1. Create a Spring Boot project

Use the Spring AI BOM that matches your application and add the MCP server starter for your chosen transport. For a WebMVC Streamable HTTP server, the artifact is:

<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>

Spring AI versions and package coordinates are release-sensitive. Import the BOM recommended for your Spring AI release instead of hard-coding an unrelated version. Spring AI 2.0 moved the Spring-specific MCP WebMVC and WebFlux artifacts into the org.springframework.ai group.

2. Add a tool service

package com.example.mcp;

import org.springframework.ai.mcp.annotation.McpTool;
import org.springframework.ai.mcp.annotation.McpToolParam;
import org.springframework.stereotype.Service;

@Service
public class WeatherService {

    @McpTool(description = "Get current temperature for a location")
    public String getTemperature(
            @McpToolParam(description = "City name", required = true)
            String city) {
        return String.format("Current temperature in %s: 22°C", city);
    }
}

The method becomes a discoverable MCP tool. The tool description helps an AI client decide when to call it, while the parameter description tells the client what input is required. Replace the illustrative fixed value with your real data source and validate external input before using it.

3. Select Streamable HTTP

spring.ai.mcp.server.protocol=STREAMABLE

With the WebMVC starter, this configures the Streamable HTTP server variant. Keep the server’s HTTP route behind your normal authentication, authorization, rate limiting, and observability layers.

4. Add an application entry point

package com.example.mcp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

Start the application with your normal Spring Boot command. An MCP client then performs protocol initialization, discovers the tool, and invokes it with a JSON argument such as {"city":"Berlin"}.

Choosing STDIO, SSE, or Streamable HTTP

Transport Best fit State and operations Trade-offs
STDIO A client launches your server as a local process Process-local communication Simple integration; requires lifecycle and process management
SSE HTTP clients and environments already built around server-sent events HTTP streaming semantics Proxy and browser behavior must be configured correctly
Streamable HTTP Modern bidirectional HTTP sessions Supports session-oriented interaction Requires correct HTTP, proxy, timeout, and session handling

The core io.modelcontextprotocol.sdk:mcp convenience module provides STDIO, SSE, and Streamable HTTP transports without requiring an external web framework. Spring AI offers starters for STDIO, WebMVC SSE, WebMVC Streamable HTTP, stateless Streamable HTTP, and WebFlux variants.

Stateful versus stateless HTTP

Choose a stateful server when a client needs a retained session across requests and your deployment can route that session consistently. Choose stateless Streamable HTTP when each request can be handled independently and horizontal scaling is more important than server-side session state. Confirm the exact starter and configuration names against the release line used by your project.

Using the framework-agnostic Java SDK

If you do not use Spring, start with the MCP SDK’s convenience module:

<dependency>
  <groupId>io.modelcontextprotocol.sdk</groupId>
  <artifactId>mcp</artifactId>
</dependency>

The SDK can also be assembled from mcp-core plus the matching Jackson 2 or Jackson 3 modules. Use the SDK BOM so all modules share a compatible version. The SDK supplies synchronous and asynchronous client/server implementations, capability negotiation, tool discovery and execution, URI-based resources, prompts, completions, structured logging, and concurrent connection management.

Do not mix arbitrary SDK module versions. Dependency coordinates and package locations can change between release lines; use the BOM and quickstart for the exact version of your application.

Adding more than one tool

Each public operation should have a narrow purpose and an explicit schema. Keep descriptions actionable and make required inputs obvious.

@Service
public class ReportService {

    @McpTool(description = "Return a report status by report identifier")
    public String getStatus(
            @McpToolParam(description = "Report identifier", required = true)
            String reportId) {
        if (reportId == null || reportId.isBlank()) {
            throw new IllegalArgumentException("reportId is required");
        }
        return "Report " + reportId + " is ready";
    }
}

Return structured data when your SDK and client configuration support it. If you return text, use a stable format so clients can parse it without guessing. Convert internal exceptions into useful protocol errors while avoiding secrets, stack traces, and sensitive records in the response.

Calling an MCP server from Java

The client side follows the same lifecycle regardless of transport:

  1. Open the transport connection.
  2. Send initialization with the supported protocol version and client capabilities.
  3. Read the server’s negotiated capabilities.
  4. List tools or other capabilities.
  5. Call a tool with validated arguments.
  6. Close the session or process cleanly.

Use the SDK’s synchronous client when a blocking workflow is acceptable. Use its asynchronous client when one process serves many concurrent conversations or when tool calls wait on network services.

Configuration checklist

  • Pin a Spring AI or MCP SDK BOM that matches the rest of the application.
  • Select exactly one intended transport starter for each deployment entry point.
  • Set spring.ai.mcp.server.protocol to the protocol mode required by the selected WebMVC setup.
  • Document whether sessions are stateful or stateless.
  • Require authentication before exposing tools that access private systems.
  • Validate every tool argument and enforce authorization inside the tool implementation.
  • Set connection, request, and downstream service timeouts.
  • Log tool name, request identifier, duration, and outcome without logging credentials or private payloads.
  • Limit concurrency and protect slow or expensive tools with queues or rate limits.

Common errors and fixes

Symptom Likely cause Fix
Dependency cannot be resolved Artifact group or version belongs to another release line Import the matching BOM and verify current coordinates; Spring AI 2.0 uses org.springframework.ai for its MCP WebMVC and WebFlux artifacts.
Client cannot discover tools Initialization failed, capability negotiation is incompatible, or the service bean was not registered Inspect startup logs, confirm the service has @Service, and verify the client and server protocol versions.
HTTP client hangs Proxy, buffering, idle timeout, or transport mismatch Confirm the endpoint’s transport, disable incompatible proxy buffering, and align client, server, and load-balancer timeouts.
Tool arguments are empty or misnamed Parameter descriptions or names do not match the client’s generated schema Use explicit @McpToolParam metadata, mark required values, and inspect the tool list returned by the server.
Requests fail only under load Blocking work consumes request threads or downstream limits are reached Use asynchronous APIs where appropriate, cap concurrency, and move slow work behind a queue.
Sessions disappear after deployment Stateful traffic is routed to different instances Use session affinity or shared session storage, or choose stateless Streamable HTTP.
Secrets appear in logs Raw arguments or headers are being logged Redact credentials and personal data; log identifiers and outcomes instead of full payloads.

Performance, reliability, and cost

Performance

Protocol overhead is only one part of latency. Tool execution, downstream HTTP calls, database queries, serialization, and proxy buffering usually dominate. Measure initialization, discovery, and tool execution separately. Reuse connections, avoid repeated discovery in a single session, and make independent downstream calls concurrently when the SDK and business logic allow it.

Reliability

Make tools idempotent when possible. Set bounded timeouts, retry only safe transient failures, and return a clear error that tells the client whether retrying is sensible. Gracefully close STDIO processes and HTTP sessions during shutdown. For stateful deployments, plan for reconnects and expired sessions.

Cost

The MCP protocol and Java SDK do not define a hosting price. Your costs come from compute, memory, network traffic, downstream services, and any model or API used by the tool. Stateless operation can simplify horizontal scaling; synchronous blocking calls can require more server capacity at the same request rate.

Or skip the browser setup

If one of your MCP tools needs website screenshots, you can expose a ScreenshotNeo call instead of managing browser binaries, consent dialogs, and page cleanup yourself. ScreenshotNeo is a website screenshot API and MCP server: one GET request returns a PNG, JPEG, WebP, or PDF. Before capture it accepts consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and each response reports its verdict and billing status in X-Page-Verdict and X-Billed headers. Its MCP server includes take_screenshot, get_page_info, and capture_pdf tools for AI clients such as Claude and Cursor.

See the ScreenshotNeo API documentation for the complete option list.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', bytes);

ScreenshotNeo supports full-page or element capture, dark mode, device presets, custom viewports, retina scale, PDF options, custom CSS and JavaScript, click and wait actions, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous jobs with signed webhooks, bulk capture, usage reporting, and an OpenAPI specification. Only clean shots are billed. The free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots.

Create a free ScreenshotNeo account and get 1,000 screenshots each month with no card required.

FAQ

Can I run an MCP server without Spring?

Yes. Use the framework-agnostic io.modelcontextprotocol.sdk:mcp module or assemble mcp-core with the matching Jackson modules.

Which transport should a desktop AI client use?

STDIO is usually the simplest when the client launches your Java process locally. Use HTTP transports when the server is remote or shared.

Should every tool method be asynchronous?

No. Synchronous methods are appropriate for short, bounded work. Use asynchronous handling when calls wait on network services or when many sessions must run concurrently.

How do I keep a tool from exposing private data?

Authenticate the MCP connection, authorize each operation and resource inside the server, validate arguments, and redact sensitive values from logs and errors.

Where should I verify dependency names?

Check the MCP Java SDK quickstart or the Spring AI release documentation for the BOM and artifact coordinates that match your project version.