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 --mcp flag)
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 |
|
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
Parameter names become argument names in the tool schema:
@Parameters(description = "Source file to analyze")
String sourceFile; (1)
// Not as clear:
// String file;
| 1 | Descriptive names make the tool easier to use. |
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
For more advanced MCP server features (HTTP transport, streaming, multiple clients), consider implementing tools directly with the standard MCP server annotations.