CLI Adapter
The CLI adapter enables Quarkus CLI applications to expose their commands as MCP tools automatically. This allows existing CLI tools to be integrated into MCP-compatible AI assistants without code changes.
Overview
The CLI adapter bridges Quarkus CLI commands and MCP by:
-
Automatically adding a
--mcpoption to your CLI’sTopCommand -
Converting all CLI subcommands into MCP tools
-
Mapping command parameters to tool arguments
-
Handling standard input/output for MCP communication
This means your CLI application can run in two modes:
-
Normal CLI mode: Traditional command-line interface (script, manual invocation)
-
MCP mode: Exposes commands as tools to MCP clients (with
--mcpflag)
Getting Started
Add the CLI adapter dependency to your Quarkus CLI project:
<dependency>
<groupId>io.quarkiverse.mcp</groupId>
<artifactId>quarkus-mcp-server-cli-adapter</artifactId>
<version>${quarkus-mcp.version}</version>
</dependency>
That’s it! The adapter automatically discovers all commands reachable from your TopCommand and exposes them as MCP tools.
Example
Here’s a complete example showing how a simple CLI application becomes MCP-enabled.
1. Create Your CLI Command
Create a standard Quarkus CLI command:
package com.example;
import java.util.concurrent.Callable;
import jakarta.inject.Inject;
import io.quarkus.picocli.runtime.annotations.TopCommand;
import picocli.CommandLine.Command;
import picocli.CommandLine.ExitCode;
import picocli.CommandLine.Parameters;
@TopCommand
@Command(name = "code-service", description = "Code assistance tool", mixinStandardHelpOptions = true)
public class CodeServiceCommand implements Callable<Integer> {
@Inject
CodeService codeService;
@Parameters(defaultValue = "java", description = "The programming language")
String language;
@Override
public Integer call() {
System.out.println(codeService.assist(language));
return 0;
}
}
2. Implement Your Service
package com.example;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class CodeService {
public String assist(String language) {
return switch (language) {
case "java" -> "System.out.println(\"Hello world!\");";
case "python" -> "print('Hello world!')";
case "javascript" -> "console.log('Hello world!');";
default -> "// Language not supported";
};
}
}
3. Run as a Traditional CLI
# Normal CLI usage
$ java -jar myapp.jar python
print('Hello world!')
$ java -jar myapp.jar --help
Usage: code-service [-hV] [--mcp] [<language>]
Code assistance tool
[<language>] The programming language
-h, --help Show this help message and exit.
--mcp Start an MCP server for the current CLI.
-V, --version Print version information and exit.
4. Run as an MCP Server
# Start as MCP server
$ java -jar myapp.jar --mcp
# Server starts and listens on stdin/stdout for MCP messages
5. Use from an MCP Client
The command becomes available as an MCP tool:
// List available tools
{
"jsonrpc": "2.0",
"method": "tools/list",
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "codeservicecommand",
"description": "Code assistance tool",
"inputSchema": {
"type": "object",
"properties": {
"language": {
"type": "string",
"description": "The programming language"
}
}
}
}
]
}
}
// Call the tool
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 2,
"params": {
"name": "codeservicecommand",
"arguments": {
"language": "python"
}
}
}
// Tool response
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "print('Hello world!')"
}
]
}
}
Under the hood
Command to Tool Mapping
| CLI Element | MCP Element | Example |
|---|---|---|
Command name |
Tool name |
|
Command description |
Tool description |
|
|
Tool argument (required if no default) |
|
|
Tool argument (named after the longest option name) |
|
Default value |
Argument default |
|
Parameter description |
Argument description |
|
Multiple Commands
For CLI applications with subcommands, all are exposed as separate tools:
@TopCommand
@Command(name = "devtools", subcommands = {
FormatCommand.class,
LintCommand.class,
TestCommand.class
})
public class DevToolsCommand implements Callable<Integer> {
// Top-level command logic
}
@Command(name = "format", description = "Format source code")
class FormatCommand implements Callable<Integer> {
@Parameters(description = "File to format")
String file;
@Option(names = {"-s", "--style"}, description = "Code style")
String style = "google";
@Override
public Integer call() {
// Format logic
}
}
@Command(name = "lint", description = "Lint source code")
class LintCommand implements Callable<Integer> {
// Lint logic
}
Each subcommand becomes an MCP tool:
-
formatcommand- Format source code -
lintcommand- Lint source code -
testcommand- Run tests
Integration with Claude Desktop
Configure Claude Desktop to use your CLI as an MCP server:
{
"mcpServers": {
"my-cli-tools": {
"command": "java",
"args": [
"-jar",
"/path/to/myapp.jar",
"--mcp"
]
}
}
}
Best Practices
Provide Clear Descriptions
Commands will be discovered by AI based on their descriptions:
@Command(
name = "analyze",
description = "Analyze code quality and suggest improvements" (1)
)
| 1 | Clear, actionable description helps AI choose the right tool. |
Use Meaningful Parameter Names
@Parameters field names become argument names in the tool schema:
@Parameters(description = "Source file to analyze")
String sourceFile; (1)
| 1 | Descriptive field names make the tool easier to use. |
For @Option, the MCP argument name is derived from the longest option name (with leading dashes stripped), not from the Java field name:
@Option(names = {"-f", "--format"}, description = "Output format")
String outputFormat; (1)
| 1 | The MCP argument name will be format (from --format), not outputFormat. |
Limitations
-
STDIO transport only: The CLI adapter uses standard input/output for MCP communication
-
Synchronous execution: Each tool call blocks until the command completes
-
No streaming: Tool responses are returned only after the command finishes
-
No concurrent tool calls: The CLI adapter redirects
System.outto capture command output, which is not safe under concurrent execution. MCP clients should not send multiple tool call requests in parallel
For more advanced MCP server features (HTTP transport, streaming, multiple clients), consider implementing tools directly with the standard MCP server annotations.