Skip to content

MCP Server

The atmosphere-mcp module is a self-contained MCP (Model Context Protocol) server implementation that rides on Atmosphere’s transport layer. It speaks the MCP 2025-03-26 JSON-RPC wire protocol directly — there is no external MCP SDK dependency — and exposes annotation-driven tools, resources, and prompt templates to AI agents over Streamable HTTP, WebSocket, or SSE.

MCP endpoints are auto-registered when atmosphere-mcp is on the classpath together with a class annotated with @Agent (from atmosphere-agent). There is no @McpServer annotation. Class-level wiring is handled by @Agent; method- and parameter-level MCP metadata uses the four annotations in this module.

<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-mcp</artifactId>
<version>${project.version}</version>
</dependency>

For Spring Boot applications, add the starter and the agent module alongside it:

<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-agent</artifactId>
<version>${project.version}</version>
</dependency>
import org.atmosphere.agent.annotation.Agent;
import org.atmosphere.mcp.annotation.McpParam;
import org.atmosphere.mcp.annotation.McpPrompt;
import org.atmosphere.mcp.annotation.McpResource;
import org.atmosphere.mcp.annotation.McpTool;
import org.atmosphere.mcp.protocol.McpMessage;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Agent(name = "my-server",
version = "1.0.0",
endpoint = "/atmosphere/mcp",
headless = true)
public class MyMcpServer {
@McpTool(name = "greet", description = "Say hello")
public String greet(@McpParam(name = "name", required = true) String name) {
return "Hello, " + name + "!";
}
@McpResource(uri = "atmosphere://server/status",
name = "Server Status",
description = "Current server status")
public String serverStatus() {
return "OK";
}
@McpPrompt(name = "summarize", description = "Summarize a topic")
public List<McpMessage> summarize(@McpParam(name = "topic") String topic) {
return List.of(
McpMessage.system("You are a summarization expert."),
McpMessage.user("Summarize: " + topic)
);
}
}

All four annotations live in org.atmosphere.mcp.annotation.

AnnotationTargetDescription
@McpToolMethodExposes a method as a callable tool (tools/call)
@McpResourceMethodExposes a method as a read-only resource (resources/read)
@McpPromptMethodExposes a method as a prompt template (prompts/get)
@McpParamParameterAnnotates method parameters with name, description, and required flag

Class-level registration is done with @Agent from atmosphere-agent — see Core Runtime and the MCP tutorial for the full wiring context.

AttributeDefaultDescription
name(required)Tool name reported to MCP clients
description""Human-readable description of what the tool does

Tool methods can return any type that Jackson can serialize (String, List, Map, records, domain objects). They may also declare injectable framework types as parameters — see Injectable parameters.

AttributeDefaultDescription
uri(required)URI or URI template identifying the resource
name""Human-readable resource name
description""Description of the resource
mimeType"text/plain"MIME type of the resource content
AttributeDefaultDescription
name(required)Prompt template name
description""Description of what the prompt does

Prompt methods return List<McpMessage>. The McpMessage type (in org.atmosphere.mcp.protocol) provides system(String) and user(String) factory methods.

AttributeDefaultDescription
name(required)Parameter name reported to MCP clients
description""Human-readable description
requiredtrueWhether the parameter is required

The MCP endpoint is registered by the AgentProcessor in atmosphere-agent during AtmosphereFramework initialization. The processor scans classes annotated with @Agent, wires their @McpTool, @McpResource, and @McpPrompt methods into an McpRegistry, and installs an McpHandler on the framework at the resolved MCP path.

@Agent.endpoint valueMCP path
Ends in /mcp (e.g. /atmosphere/mcp)Used verbatim
Any other value, or empty/atmosphere/agent/{name}/mcp
ModuleWhy
atmosphere-mcpThe annotations, registry, and protocol runtime
atmosphere-agentThe AgentProcessor that scans @Agent classes and registers the MCP handler
atmosphere-runtime (transitive)The transport layer

For Spring Boot apps, atmosphere-spring-boot-starter auto-configures the AtmosphereServlet, performs annotation scanning through Spring’s component scanning, and installs the agent processor automatically. Set atmosphere.packages in application.properties so the framework knows which packages to scan:

atmosphere.packages=org.atmosphere.mcp,com.example.mcp

There is no MCP-specific Spring Boot auto-configuration class and no McpProperties binding — all MCP behavior is controlled through @Agent attributes and the annotations in this module.

TransportHow to connect
Streamable HTTP (recommended, MCP 2025-03-26)POST http://host:port/atmosphere/mcp
WebSocketws://host:port/atmosphere/mcp
SSEGET http://host:port/atmosphere/mcp

All three are served from the same registered MCP path. The McpHandler dispatches POST bodies as JSON-RPC requests (returning either application/json or text/event-stream responses based on the Accept header), uses GET to open an SSE notification stream, and uses DELETE to terminate a session. Per-session state is tracked via the Mcp-Session-Id header, with a 30-minute idle TTL and a 10,000-session cap by default.

Agents get automatic reconnection, heartbeats, and transport fallback from Atmosphere’s transport layer.

@McpTool methods can declare framework types as method parameters. These are auto-injected at invocation time and excluded from the JSON schema advertised to MCP clients — the AI agent never sees them.

Parameter typeWhat’s injectedRequires
BroadcasterBroadcasterFactory.lookup(topic, true)A @McpParam(name = "topic") argument in the call
StreamingSessionBroadcasterStreamingSession wrapping the topic’s BroadcasterA @McpParam(name = "topic") argument and atmosphere-ai on the classpath
AtmosphereConfigThe framework’s AtmosphereConfigNothing
BroadcasterFactoryThe framework’s BroadcasterFactoryNothing
AtmosphereFrameworkThe framework instanceNothing
@McpTool(name = "broadcast", description = "Send a message to a chat topic")
public String broadcast(
@McpParam(name = "message") String message,
@McpParam(name = "topic") String topic,
Broadcaster broadcaster) {
broadcaster.broadcast(message);
return "sent to " + topic;
}

With atmosphere-ai on the classpath, inject a StreamingSession bound to the topic’s broadcaster:

@McpTool(name = "ask_ai", description = "Ask AI and stream answer to a topic")
public String askAi(
@McpParam(name = "question") String question,
@McpParam(name = "topic") String topic,
StreamingSession session) {
Thread.startVirtualThread(() -> {
var request = ChatCompletionRequest.builder(model).user(question).build();
client.streamChatCompletion(request, session);
});
return "streaming to " + topic;
}

McpRegistry supports imperative registration for tools built at runtime:

var registry = new McpRegistry();
registry.registerTool("greet", "Greet a user by name",
List.of(new McpRegistry.ParamEntry("name", "User name", true, String.class)),
args -> "Hello, " + args.get("name") + "!"
);
registry.registerResource("atmosphere://app/version",
"App Version", "Current application version", "text/plain",
args -> "1.0.0"
);
registry.registerPrompt("welcome", "Welcome message for new users", List.of(),
args -> List.of(
McpMessage.system("You are a friendly assistant."),
McpMessage.user("Welcome the user warmly.")
)
);

Annotation-based and programmatic registrations coexist in the same registry.

ClassRole
org.atmosphere.mcp.runtime.McpHandlerAtmosphereHandler that dispatches Streamable HTTP / WebSocket / SSE to the JSON-RPC protocol
org.atmosphere.mcp.runtime.McpProtocolHandlerCore JSON-RPC processing shared by all transports
org.atmosphere.mcp.runtime.McpSessionPer-client session state (Mcp-Session-Id, pending notifications, TTL)
org.atmosphere.mcp.runtime.McpWebSocketHandlerWebSocket-specific frame handling
org.atmosphere.mcp.runtime.McpTracingOpenTelemetry integration (optional)
org.atmosphere.mcp.registry.McpRegistryAnnotation scanning + programmatic registration
org.atmosphere.mcp.protocol.McpMessagePrompt message factories (system(), user())
org.atmosphere.mcp.protocol.McpMethodMCP method name constants
org.atmosphere.mcp.protocol.JsonRpcJSON-RPC protocol helpers
org.atmosphere.mcp.BiDirectionalToolBridgeServer-to-client tool call bridge
org.atmosphere.mcp.ToolResponseHandlerAtmosphereHandler for bidirectional tool responses
org.atmosphere.mcp.bridge.McpStdioBridgestdio-to-HTTP bridge JAR entry point

BiDirectionalToolBridge lets the server invoke tools on connected clients (browser JavaScript, for example) and receive results asynchronously:

var bridge = new BiDirectionalToolBridge(); // 30s default
var withTimeout = new BiDirectionalToolBridge(Duration.ofSeconds(10));
CompletableFuture<String> result = bridge.callClientTool(
resource, "getLocation", Map.of("highAccuracy", true));

The bridge tracks pending futures in a ConcurrentHashMap, generates UUIDs for correlation, and completes futures exceptionally on timeout (TimeoutException) or client-reported errors (ToolCallException). To receive client responses, register a ToolResponseHandler explicitly:

framework.addAtmosphereHandler("/_mcp/tool-response",
new ToolResponseHandler(bridge));

McpTracing wraps every tools/call, resources/read, and prompts/get invocation in an OTel trace span. Add opentelemetry-api to your classpath:

<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
<optional>true</optional>
</dependency>

With the Spring Boot starter, McpTracing is auto-configured when an OpenTelemetry bean is present.

Span attributes:

AttributeDescription
mcp.tool.nameTool/resource/prompt name
mcp.tool.type"tool", "resource", or "prompt"
mcp.tool.arg_countNumber of arguments provided
mcp.tool.errortrue if invocation failed

Works with Claude Desktop, VS Code Copilot, Cursor, and any MCP-compatible agent:

{
"mcpServers": {
"my-server": { "url": "http://localhost:8080/atmosphere/mcp" }
}
}

For clients that only support stdio, build the bridge JAR with ./mvnw package -Pstdio-bridge -DskipTests -pl modules/mcp and point the client at the resulting atmosphere-mcp-*-stdio-bridge.jar.

  • samples/spring-boot-mcp-server/ — a complete Spring Boot MCP server exposing chat administration tools, server resources, and prompt templates, with a React frontend for human users.