Quarkus MCP Server

repository·main·Indexed 41 days ago

https://github.com/quarkiverse/quarkus-mcp-server

A Quarkus extension for implementing Model Context Protocol (MCP) server features using declarative annotations or programmatic APIs. It supports HTTP/SSE, Streamable HTTP, and STDIO transport protocols, allowing LLM applications to integrate with external data sources and tools via @Tool, @Prompt, and @Resource annotations.

Tokens
83.1K
Snippets
221
Records
298
Agent score
3%

What's inside Quarkus MCP Server

  1. What is the Model Context Protocol (MCP)?

    main
    The Model Context Protocol (MCP) is an open protocol created by Anthropic that standardizes how applications provide knowledge and capabilities (context) to Large Language Models (LLMs). It allows AI assistants (like Claude) to connect to external data sources and tools using a universal, standardized interface rather than requiring custom integration code for every source.
  2. What is the stdio -> SSE MCP Proxy?

    main
    The stdio -> SSE MCP Proxy is a utility that acts as a bridge between two different Model Context Protocol (MCP) transport layers. It allows an MCP client that communicates via stdio (standard input/output) to interact with an MCP server that uses HTTP/SSE (Server-Sent Events) transport. The proxy forwards all incoming MCP messages from the client to the target SSE endpoint and pipes the server's responses back to the client's standard output.
  3. Implement Multi Round-Trip Requests (MRTR) for stateless clients

    main

    In stateless protocol versions, the server cannot initiate requests (Sampling, Elicitation, Roots) directly. Instead, it must use the MRTR pattern:

    1. Detect Mode: Use isServerInitiatedRequestSupported() on the feature interface. If it returns false, you are in stateless mode.
    2. Initial Request: If inputResponses().isEmpty() is true, the server must throw an InputRequiredException using the inputRequired() builder. You can attach one or more requests (Sampling, Elicitation, or Roots) to this exception.
    3. Retry: The client will gather the required input and retry the original tool call.
    4. Retrieve Data: On the retry, use inputResponses() to retrieve the results (e.g., getSamplingResponse(key), getElicitationResponse(key), or getRootsResponse(key)).

    Note: Use setRequestState(String) on the builder to provide an opaque state string that the client will echo back, helping you identify the context of the retry.

    // Stateless MRTR Pattern Example
    if (sampling.isServerInitiatedRequestSupported()) {
        // Use standard blocking/async API
    } else if (sampling.inputResponses().isEmpty()) {
        // First attempt: Throw exception to trigger client input
        throw sampling.inputRequired()
            .addSamplingRequest("answer", sampling.requestBuilder()...
                .build())
            .build();
    }
    
    // Second attempt (Retry): Get the response
    return sampling.inputResponses().getSamplingResponse("answer").content().asText().text();
  4. How the CLI Adapter maps commands to MCP tools

    main

    The adapter automatically translates Picocli/Quarkus CLI elements into MCP tool definitions using the following mapping logic:

    CLI ElementMCP ElementLogic/Example
    Command nameTool namecode-service becomes codeservicecommand
    Command descriptionTool descriptionUses the @Command(description = "...") value
    @ParametersTool argumentBecomes a required argument unless a defaultValue is provided
    @OptionTool argumentNamed after the longest option name (e.g., --format becomes format)
    Default valueArgument defaultdefaultValue = "java" makes the argument optional in the MCP schema
    Parameter descriptionArgument descriptionUses the description attribute from the annotation

    Supported Parameter Types:

    • String
    • int, Integer
    • boolean, Boolean
    • long, Long
    • double, Double
    • Arrays and Lists of the above types
  5. How Filters and Initial Checks work together

    main

    The Quarkus MCP Server provides two complementary mechanisms for access control:

    1. Filters: Control feature visibility. They determine which tools, resources, or prompts are visible to a specific client based on the connection context, client capabilities, or request headers.
    2. Initial Checks: Validate connections. They run during the initialization handshake and can reject a connection entirely before any capabilities are exposed.

    Use Initial Checks for authentication and connection-level requirements (e.g., 'Is this client allowed to connect?'), and Filters for fine-grained feature access (e.g., 'Is this client allowed to see this specific tool?').

  6. How MCP Capability Negotiation works

    main

    MCP uses a negotiation process to establish what features are available between the client and the server.

    Standard Initialization Sequence

    1. Client sends initialize request: Includes protocolVersion, clientInfo, and client capabilities (e.g., sampling, elicitation, roots).
    2. Server responds with its capabilities: Includes protocolVersion, serverInfo, and server capabilities (e.g., tools, resources, prompts, logging).
    3. Client sends initialized notification: Signals the handshake is complete.

    Stateless Discovery (Protocol version 2026-07-28+)

    Stateless clients use server/discover instead of the initialize/initialized handshake. The server responds with supported versions, capabilities, and server info, including caching hints (ttlMs and cacheScope).

    Capability Types

    • Client Capabilities (what the server can request from the client):
      • sampling: Invoking the client-side LLM from the server.
      • elicitation: Collecting user input (forms, confirmations).
      • roots: Accessing client root directories.
    • Server Capabilities (what the client can request from the server):
      • tools: Executable functions.
      • resources: Readable/subscribable data.
      • prompts: Reusable prompt templates.
      • logging: Server-to-client log messages.
    /* Standard Initialize Request */
    {
      "jsonrpc": "2.0",
      "id": 1,
      "method": "initialize",
      "params": {
        "protocolVersion": "{spec-version}",
        "clientInfo": {
          "name": "my-mcp-client",
          "version": "1.0.0"
        },
        "capabilities": {
          "sampling": {},
          "elicitation": {},
          "roots": { "listChanged": true }
        }
      }
    }
    
    /* Stateless Discovery Request */
    {
      "jsonrpc": "2.0",
      "id": 1,
      "method": "server/discover",
      "params": {
        "_meta": {
          "io.modelcontextprotocol/protocolVersion": "2026-07-28"
        }
      }
    }
  7. Configure streamable mode: Auto-initialization

    main

    The quarkus.mcp.server.http.streamable.auto-init property enables a mode that simulates stateless communication. When set to true, the server performs automatic initialization if the first message from the client is not initialize. A new MCP session is created for each request, skipping formal capability negotiation and protocol version agreement.

    Warning: This mode is not efficient and may cause some features to malfunction. It will be deprecated once stateless mode is officially codified in the MCP specification.

    Clients can still provide info via _meta fields (io.modelcontextprotocol/clientInfo and io.modelcontextprotocol/clientCapabilities) and specify the protocol version via the Mcp-Protocol-Version header.

    # Enable automatic initialization for stateless-like behavior
    quarkus.mcp.server.http.streamable.auto-init=true
  8. Automatically wrap business errors with @WrapBusinessError

    main

    The @io.quarkiverse.mcp.server.WrapBusinessError annotation automatically converts exceptions thrown from a @Tool method into ToolCallException. This eliminates the need to manually catch and rethrow ToolCallException for every error condition.

    Configuration Options:

    • Selective Wrapping: Pass specific exception classes to the annotation to only wrap those types (e.g., @WrapBusinessError(NullPointerException.class)).
    • Excluding Exceptions: Use the unless parameter to prevent specific exception types from being wrapped (e.g., @WrapBusinessError(unless = McpException.class)). This allows protocol errors to bypass the wrapper and propagate as JSON-RPC errors.
    • Scope: Can be applied to individual methods or at the class level to cover all tool methods in that class. Method-level annotations override class-level settings.
    import io.quarkiverse.mcp.server.Tool;
    import io.quarkiverse.mcp.server.WrapBusinessError;
    import io.quarkiverse.mcp.server.McpException;
    
    public class MyTools {
    
        @WrapBusinessError(unless = McpException.class)
        @Tool(description = "Process an order")
        String processOrder(String orderId) {
            if (orderId == null || orderId.isEmpty()) {
                // This will be automatically wrapped into a ToolCallException
                throw new IllegalArgumentException("Order ID cannot be empty");
            }
            return "Order processed";
        }
    }
  9. Namespace metadata using prefixes

    main

    To avoid naming collisions between different providers or modules, use the prefix attribute in @MetaField. This creates a namespaced key in the resulting metadata object.

    Warning: Do not use reserved prefixes such as modelcontextprotocol.io/ or tools.mcp.com/, as these are reserved for the MCP specification.

    import io.quarkiverse.mcp.server.Tool;
    import io.quarkiverse.mcp.server.MetaField;
    
    public class MyTools {
    
        @MetaField(prefix = "mycompany.com/", name = "tier", value = "enterprise")
        @MetaField(prefix = "monitoring.io/", name = "tracked", value = "true")
        @Tool(description = "Enterprise tier tool")
        String enterpriseTool() {
            return "Enterprise result";
        }
    }
  10. Use Elicitation to request user input

    main

    Elicitation allows the server to request additional information from the user via the client. It supports two modes:

    1. Form Mode

    Used for collecting structured, non-sensitive data (e.g., usernames, ages, preferences) through a form-like interface.

    • Check Support: Use elicitation.isFormModeSupported().
    • Requesting: Use elicitation.requestBuilder() to add schema properties (e.g., StringSchema, NumberSchema, BooleanSchema, SingleSelectEnumSchema, MultiSelectEnumSchema).
    • Handling Response: Check response.actionAccepted() to see if the user submitted or cancelled. Use response.content().getString(key) or similar to extract values.

    2. URL Mode

    Used for sensitive interactions that must not pass through the MCP client (e.g., API keys, OAuth, payments). The server directs the user to an external URL.

    • Check Support: Use elicitation.isUrlModeSupported().
    • Requesting: Use elicitation.urlRequestBuilder() to set a message and a URL.
    • Completion: Since this is out-of-band, the server must notify the client when the interaction is finished by injecting the ElicitationCompletion CDI bean and calling elicitationCompletion.send(elicitationId) from a callback (e.g., a REST endpoint).
    // Basic Form Mode Example
    ElicitationRequest request = elicitation.requestBuilder()
        .setMessage("Please provide your information:")
        .addSchemaProperty("username", new StringSchema(true))
        .addSchemaProperty("email", new StringSchema(true))
        .build();
    
    ElicitationResponse response = request.sendAndAwait();
    
    if (response.actionAccepted()) {
        String username = response.content().getString("username");
        return "Hello " + username;
    }
  11. How streamable auto-initialization works

    main

    The quarkus.mcp.server.http.streamable.auto-init property allows the server to simulate stateless communication by performing automatic initialization.

    Behavior:

    • If set to true, the server performs automatic initialization when the first message from the client is not initialize.
    • A new MCP session is created for each request.
    • Formal capability negotiation and protocol version agreement are skipped.
    • Client Requirements: The client can optionally provide info and capabilities via _meta fields (io.modelcontextprotocol/clientInfo and io.modelcontextprotocol/clientCapabilities). The protocol version can be specified using the Mcp-Protocol-Version header.

    Warning: This is intended to simulate statelessness and is not highly efficient. Some features may not work properly. This property is slated for deprecation once stateless mode is officially codified in the MCP specification.

    # Enable automatic initialization for stateless-like behavior
    quarkus.mcp.server.http.streamable.auto-init=true
  12. What are Tool Guardrails?

    main

    Tool guardrails allow you to intercept, validate, or transform data in an MCP server at two specific stages:

    • Input Guardrails: Intercept tools/call requests to validate or transform arguments before the tool implementation is executed.
    • Output Guardrails: Intercept the tool's result to validate or transform the response before it is returned to the client.

    Common use cases include data validation (e.g., email formats), sanitizing input to prevent injection, normalizing argument casing, enforcing business rules, and filtering sensitive information from responses.