Quarkus MCP Server
repository·main·Indexed 41 days ago
https://github.com/quarkiverse/quarkus-mcp-serverA 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.
What's inside Quarkus MCP Server
- 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.
What is the stdio -> SSE MCP Proxy?
mainThestdio -> SSE MCP Proxyis a utility that acts as a bridge between two different Model Context Protocol (MCP) transport layers. It allows an MCP client that communicates viastdio(standard input/output) to interact with an MCP server that usesHTTP/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.Implement Multi Round-Trip Requests (MRTR) for stateless clients
mainIn stateless protocol versions, the server cannot initiate requests (Sampling, Elicitation, Roots) directly. Instead, it must use the MRTR pattern:
- Detect Mode: Use
isServerInitiatedRequestSupported()on the feature interface. If it returnsfalse, you are in stateless mode. - Initial Request: If
inputResponses().isEmpty()is true, the server must throw anInputRequiredExceptionusing theinputRequired()builder. You can attach one or more requests (Sampling, Elicitation, or Roots) to this exception. - Retry: The client will gather the required input and retry the original tool call.
- Retrieve Data: On the retry, use
inputResponses()to retrieve the results (e.g.,getSamplingResponse(key),getElicitationResponse(key), orgetRootsResponse(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();- Detect Mode: Use
How the CLI Adapter maps commands to MCP tools
mainThe adapter automatically translates Picocli/Quarkus CLI elements into MCP tool definitions using the following mapping logic:
CLI Element MCP Element Logic/Example Command name Tool name code-servicebecomescodeservicecommandCommand description Tool description Uses the @Command(description = "...")value@ParametersTool argument Becomes a required argument unless a defaultValueis provided@OptionTool argument Named after the longest option name (e.g., --formatbecomesformat)Default value Argument default defaultValue = "java"makes the argument optional in the MCP schemaParameter description Argument description Uses the descriptionattribute from the annotationSupported Parameter Types:
Stringint,Integerboolean,Booleanlong,Longdouble,Double- Arrays and Lists of the above types
How Filters and Initial Checks work together
mainThe Quarkus MCP Server provides two complementary mechanisms for access control:
- 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.
- 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?').
How MCP Capability Negotiation works
mainMCP uses a negotiation process to establish what features are available between the client and the server.
Standard Initialization Sequence
- Client sends
initializerequest: IncludesprotocolVersion,clientInfo, and clientcapabilities(e.g.,sampling,elicitation,roots). - Server responds with its capabilities: Includes
protocolVersion,serverInfo, and servercapabilities(e.g.,tools,resources,prompts,logging). - Client sends
initializednotification: Signals the handshake is complete.
Stateless Discovery (Protocol version 2026-07-28+)
Stateless clients use
server/discoverinstead of theinitialize/initializedhandshake. The server responds with supported versions, capabilities, and server info, including caching hints (ttlMsandcacheScope).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" } } }- Client sends
Configure streamable mode: Auto-initialization
mainThe
quarkus.mcp.server.http.streamable.auto-initproperty enables a mode that simulates stateless communication. When set totrue, the server performs automatic initialization if the first message from the client is notinitialize. 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
_metafields (io.modelcontextprotocol/clientInfoandio.modelcontextprotocol/clientCapabilities) and specify the protocol version via theMcp-Protocol-Versionheader.# Enable automatic initialization for stateless-like behavior quarkus.mcp.server.http.streamable.auto-init=trueAutomatically wrap business errors with @WrapBusinessError
mainThe
@io.quarkiverse.mcp.server.WrapBusinessErrorannotation automatically converts exceptions thrown from a@Toolmethod intoToolCallException. This eliminates the need to manually catch and rethrowToolCallExceptionfor 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
unlessparameter 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"; } }- Selective Wrapping: Pass specific exception classes to the annotation to only wrap those types (e.g.,
Namespace metadata using prefixes
mainTo avoid naming collisions between different providers or modules, use the
prefixattribute in@MetaField. This creates a namespaced key in the resulting metadata object.Warning: Do not use reserved prefixes such as
modelcontextprotocol.io/ortools.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"; } }Use Elicitation to request user input
mainElicitation 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. Useresponse.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
ElicitationCompletionCDI bean and callingelicitationCompletion.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; }- Check Support: Use
How streamable auto-initialization works
mainThe
quarkus.mcp.server.http.streamable.auto-initproperty 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 notinitialize. - 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
_metafields (io.modelcontextprotocol/clientInfoandio.modelcontextprotocol/clientCapabilities). The protocol version can be specified using theMcp-Protocol-Versionheader.
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- If set to
What are Tool Guardrails?
mainTool guardrails allow you to intercept, validate, or transform data in an MCP server at two specific stages:
- Input Guardrails: Intercept
tools/callrequests 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.
- Input Guardrails: Intercept