Skip to content
jfarcand edited this page Feb 21, 2026 · 4 revisions

MCP Server — Expose Tools, Resources, and Prompts to AI Agents

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.

Quick Start

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.

Annotations

@McpServer

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

@McpTool

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.

@McpResource

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

@McpPrompt

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.

@McpParam

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 @McpParam for required parameters — the framework reads parameter names from bytecode.

Example: Full MCP Server

@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)
        );
    }
}

Spring Boot Integration

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.mcp

The @McpServer class is discovered and registered automatically. Connect at ws://localhost:8080/atmosphere/mcp.

React Frontend with useAtmosphere

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.

Programmatic Registration

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.

Transports

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

Streamable HTTP

The Streamable HTTP transport follows the MCP 2025-03-26 specification:

  • POST — send JSON-RPC requests. Set Accept: text/event-stream to receive SSE responses
  • GET — open an SSE stream for server-initiated notifications
  • DELETE — terminate the session
  • Sessions tracked via the Mcp-Session-Id header

stdio Bridge

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/mcp

Claude Desktop config.json with stdio:

{
  "mcpServers": {
    "atmosphere": {
      "command": "java",
      "args": ["-jar", "atmosphere-mcp-stdio-bridge.jar",
               "http://localhost:8083/atmosphere/mcp"]
    }
  }
}

Connecting AI Agents

Claude Desktop

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"]
    }
  }
}

Any MCP Client

The server speaks standard JSON-RPC 2.0 over WebSocket. The handshake follows the MCP specification:

  1. Client sends initialize → server responds with capabilities
  2. Client sends initialized notification
  3. Client calls tools/list, resources/list, prompts/list to discover capabilities
  4. Client invokes tools/call, resources/read, prompts/get as needed

Architecture

The MCP module builds on Atmosphere's transport layer:

  • McpRegistry — scans @McpServer classes 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 and Mcp-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.

See Also

Clone this wiki locally