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 --mcp option to your CLI’s TopCommand

  • 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

code-servicecodeservicecommand

Command description

Tool description

"Code assistance tool"

@Parameters

Tool argument (required if no default)

String language"language": {"type": "string"}

@Option

Tool argument (named after the longest option name)

@Option(names = {"-f", "--format"}) String fmt"format": {…​}

Default value

Argument default

defaultValue = "java" → optional argument

Parameter description

Argument description

description = "The programming language"

Supported Parameter Types

The adapter supports standard Java types:

  • String

  • int, Integer

  • boolean, Boolean

  • long, Long

  • double, Double

  • Arrays and Lists of the above types

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.

Set Sensible Defaults

Defaults make parameters optional:

@Option(
    names = {"-l", "--language"},
    description = "Programming language",
    defaultValue = "java" (1)
)
String language;
1 With a default, the AI can call the tool without specifying the language.

Handle Errors Gracefully

Return meaningful error messages:

@Override
public Integer call() {
    try {
        // Process
        return ExitCode.OK;
    } catch (IllegalArgumentException e) {
        System.err.println("Error: " + e.getMessage()); (1)
        return ExitCode.USAGE;
    }
}
1 Error messages appear in the tool response.

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.out to 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.