-
-
Notifications
You must be signed in to change notification settings - Fork 755
MCP Server
Atmosphere 4.0 includes a built-in Model Context Protocol (MCP) server that lets AI agents (Claude Desktop, GitHub Copilot, custom clients) invoke tools, read resources, and use prompt templates — all over WebSocket with automatic SSE fallback.
You annotate plain Java methods with @McpTool, @McpResource, or @McpPrompt, and Atmosphere handles the JSON-RPC 2.0 protocol, transport negotiation, and reconnection.
Add the dependency:
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-mcp</artifactId>
<version>4.0.1</version>
</dependency>Annotate a class:
@McpServer(name = "my-server", path = "/atmosphere/mcp")
public class MyMcpServer {
@McpTool(name = "get_time", description = "Get the current server time")
public String getTime(
@McpParam(name = "timezone", description = "IANA timezone", required = false) String tz
) {
var zone = tz != null ? ZoneId.of(tz) : ZoneId.systemDefault();
return Instant.now().atZone(zone).format(DateTimeFormatter.RFC_1123_DATE_TIME);
}
}Connect from Claude Desktop, Copilot, or any MCP client at ws://localhost:8080/mcp.
Marks a class as an MCP endpoint. One class per server.
| Attribute | Default | Description |
|---|---|---|
name |
(required) | Server name reported during initialize
|
version |
"1.0.0" |
Server version |
path |
"/mcp" |
WebSocket/SSE endpoint path |
Exposes a method as a callable tool. AI agents discover tools via tools/list and invoke them via tools/call.
| Attribute | Description |
|---|---|
name |
Tool name (how agents refer to it) |
description |
Human-readable description (agents use this to decide when to call it) |
The method return value is serialized to JSON and sent back to the agent. Parameters are automatically mapped from the agent's JSON arguments.
Exposes a method as a read-only resource. Agents discover resources via resources/list and read them via resources/read.
| Attribute | Default | Description |
|---|---|---|
uri |
(required) | Resource URI (e.g., atmosphere://server/status) |
name |
(required) | Human-readable name |
description |
What this resource provides | |
mimeType |
"text/plain" |
Content type |
Exposes a method as a prompt template. Returns a List<McpMessage> with system and user messages.
| Attribute | Description |
|---|---|
name |
Prompt name |
description |
What this prompt does |
Use McpMessage.system(...) and McpMessage.user(...) to build the message list.
Annotates method parameters with metadata for the agent.
| Attribute | Default | Description |
|---|---|---|
name |
(required) | Parameter name in the JSON schema |
description |
Helps the agent understand what to pass | |
required |
true |
Whether the agent must provide this parameter |
Tip: If you compile with
-parameters, you can omit@McpParamfor required parameters — the framework reads parameter names from bytecode.
@McpServer(name = "notes-server", version = "1.0.0", path = "/atmosphere/mcp")
public class NotesMcpServer {
private final Map<String, String> notes = new ConcurrentHashMap<>();
// ── Tools ──
@McpTool(name = "save_note", description = "Save a note with a title")
public Map<String, Object> saveNote(
@McpParam(name = "title", description = "Note title") String title,
@McpParam(name = "content", description = "Note content") String content
) {
notes.put(title, content);
return Map.of("saved", title);
}
@McpTool(name = "list_notes", description = "List all saved notes")
public Map<String, String> listNotes() {
return Map.copyOf(notes);
}
// ── Resources ──
@McpResource(uri = "atmosphere://notes/count",
name = "Note Count",
description = "Number of stored notes",
mimeType = "application/json")
public String noteCount() {
return Map.of("count", notes.size()).toString();
}
// ── Prompts ──
@McpPrompt(name = "summarize", description = "Summarize all notes")
public List<McpMessage> summarize() {
var list = notes.isEmpty() ? "No notes." : String.join("\n", notes.values());
return List.of(
McpMessage.system("Summarize concisely."),
McpMessage.user("Notes:\n" + list)
);
}
}Add the starter and MCP module:
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-spring-boot-starter</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-mcp</artifactId>
<version>4.0.1</version>
</dependency>Configure the scanning package:
atmosphere.packages=com.example.mcpThe @McpServer class is discovered and registered automatically. Connect at ws://localhost:8080/atmosphere/mcp.
The Spring Boot MCP Server sample ships with a React frontend that uses the useAtmosphere hook to provide a live chat UI alongside the MCP endpoint:
import { useAtmosphere } from 'atmosphere.js/react';
const { data, state, push } = useAtmosphere<ChatMessage>({
request: {
url: '/atmosphere/chat',
transport: 'websocket',
fallbackTransport: 'long-polling',
trackMessageLength: true,
contentType: 'application/json',
},
});This demonstrates a common pattern: MCP tools + real-time UI in the same application. AI agents connect to /atmosphere/mcp to invoke tools (list users, broadcast messages, ban users), while human users interact through the React chat UI at /atmosphere/chat.
See Framework Hooks — React, Vue, Svelte for the full hooks API.
For dynamic tools or when you prefer lambdas over annotations, register tools, resources, and prompts programmatically:
McpRegistry registry = McpRegistry.get();
// Register a tool with a lambda
registry.registerTool("greet", "Greet a user by name",
List.of(new McpRegistry.ParamEntry("name", "User name", true)),
args -> "Hello, " + args.get("name") + "!"
);
// Register a resource
registry.registerResource("atmosphere://app/version",
"App Version", "Current application version", "text/plain",
() -> "4.0.0"
);
// Register a prompt
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.")
)
);Programmatic and annotation-based registrations coexist — agents see all of them via tools/list.
The MCP module supports three transports:
| Transport | URL | Use Case |
|---|---|---|
| WebSocket | ws://host/atmosphere/mcp |
Full-duplex, lowest latency |
| Streamable HTTP | POST http://host/atmosphere/mcp |
MCP spec 2025-03-26, SSE responses |
| stdio (via bridge) | java -jar atmosphere-mcp-stdio-bridge.jar <url> |
Claude Desktop, VS Code |
The Streamable HTTP transport follows the MCP 2025-03-26 specification:
-
POST — send JSON-RPC requests. Set
Accept: text/event-streamto receive SSE responses - GET — open an SSE stream for server-initiated notifications
- DELETE — terminate the session
- Sessions tracked via the
Mcp-Session-Idheader
For MCP clients that only support stdio (e.g., some Claude Desktop configurations), the stdio bridge translates between stdin/stdout and the Streamable HTTP endpoint:
java -jar atmosphere-mcp-stdio-bridge.jar http://localhost:8083/atmosphere/mcpClaude Desktop config.json with stdio:
{
"mcpServers": {
"atmosphere": {
"command": "java",
"args": ["-jar", "atmosphere-mcp-stdio-bridge.jar",
"http://localhost:8083/atmosphere/mcp"]
}
}
}Via WebSocket:
{
"mcpServers": {
"atmosphere": {
"transport": "websocket",
"url": "ws://localhost:8080/atmosphere/mcp"
}
}
}Via Streamable HTTP:
{
"mcpServers": {
"atmosphere": {
"transport": "streamable-http",
"url": "http://localhost:8080/atmosphere/mcp"
}
}
}Via stdio bridge:
{
"mcpServers": {
"atmosphere": {
"command": "java",
"args": ["-jar", "atmosphere-mcp-stdio-bridge.jar",
"http://localhost:8080/atmosphere/mcp"]
}
}
}The server speaks standard JSON-RPC 2.0 over WebSocket. The handshake follows the MCP specification:
- Client sends
initialize→ server responds with capabilities - Client sends
initializednotification - Client calls
tools/list,resources/list,prompts/listto discover capabilities - Client invokes
tools/call,resources/read,prompts/getas needed
The MCP module builds on Atmosphere's transport layer:
-
McpRegistry— scans@McpServerclasses at startup, indexes tools/resources/prompts (annotation and programmatic) -
McpProtocolHandler— parses JSON-RPC messages, dispatches to annotation methods or lambda handlers -
McpWebSocketHandler— handles WebSocket frames -
McpHandler— handles Streamable HTTP (POST/GET/DELETE) and SSE fallback -
McpSession— tracks session state andMcp-Session-Id -
McpStdioBridge— stdin/stdout ↔ HTTP adapter for Claude Desktop -
JsonRpc— serializes/deserializes JSON-RPC 2.0 request and response objects
Because MCP runs over Atmosphere's transport, you get automatic reconnection, heartbeats, and transport fallback for free — features that raw MCP servers don't have.
-
Framework Hooks — React, Vue, Svelte —
useAtmospherehook used by the MCP sample frontend - AI / LLM Streaming — stream LLM responses token-by-token
- Understanding @ManagedService — real-time endpoints
- Sample: Spring Boot MCP Server