-
Notifications
You must be signed in to change notification settings - Fork 55
Description
MCP CLI Adapter Bug Report: Parameter Name Mapping Issue
Summary
The MCP CLI adapter has two related bugs that cause parameter mapping failures:
- Primary Bug: Uses Java field names instead of Picocli option names for MCP tool parameters
- Secondary Issue: Uses the first option name (often short form like
-c) instead of the longest/preferred option name (like--cluster)
Bug Locations
Primary Bug
File: repo/quarkus-mcp-server/cli-adapter/deployment/src/main/java/io/quarkiverse/mcp/server/cli/adapter/deployment/McpServerCliAdapterProcessor.java
Lines: 314-325 (in wrapCommand method)
Secondary Issue
File: repo/quarkus-mcp-server/cli-adapter/deployment/src/main/java/io/quarkiverse/mcp/server/cli/adapter/deployment/CommandUtil.java
Lines: 97-99 (in listOptions method)
Root Cause Analysis
Current Implementation (Lines 314-325)
for (Entry<String, FieldInfo> entry : options.entrySet()) {
FieldInfo option = entry.getValue();
String optionName = option.name().toString(); // ← BUG: Uses field name
String optionDescription = String.join("\n",
option.annotation(DotNames.OPTION).value("description").asStringArray());
executeMethod.getParameterAnnotations(optionIndex).addAnnotation(ToolArg.class)
.add("name", optionName.replaceAll("^-*", "")) // ← Uses field name
.add("description", optionDescription)
.add("required", false);
optionIndex++;
}The Problem
- Line 316:
option.name().toString()returns the Java field name (e.g.,clusterNameOrId)optionis aFieldInfoobject (from Jandex bytecode analysis)FieldInfo.name()returns the Java field identifier, NOT the Picocli option name
- Line 321: This field name is used as the MCP tool parameter name
- Line 352: When calling the command, it correctly uses
entry.getKey()which contains the Picocli option name (e.g.,--cluster)
Parameter Flow
MCP Tool Call:
{"cluster": "eip-reflup"} ← User provides this
↓
MCP Tool Parameter Name: "clusterNameOrId" ← Generated from field name (WRONG)
↓
Command Line Argument: "--cluster" ← From entry.getKey() (CORRECT)
↓
Result: Parameter mismatch - MCP expects "clusterNameOrId" but should expect "cluster"
Affected Code Example
NamespaceNodeInfoCommand.java
@Option(names = { "-c", "--cluster" },
description = "Name or ID of the IBM Cloud Kubernetes cluster...")
private String clusterNameOrId; // ← Field name differs from option nameGenerated MCP Tool (Current - WRONG)
public String namespace_namespacenodeinfocommand(
@ToolArg(name = "namespaces", ...) String namespaces,
@ToolArg(name = "clusterNameOrId", ...) String clusterNameOrId, // ← WRONG: Uses field name
@ToolArg(name = "jsonOutput", ...) String jsonOutput,
@ToolArg(name = "dryRun", ...) String dryRun) {
HashMap map = new HashMap();
map.put("--cluster", clusterNameOrId); // ← Maps to correct CLI option
// ...
}What Should Be Generated (CORRECT)
public String namespace_namespacenodeinfocommand(
@ToolArg(name = "namespaces", ...) String namespaces,
@ToolArg(name = "cluster", ...) String cluster, // ← CORRECT: Uses option name
@ToolArg(name = "json", ...) String json,
@ToolArg(name = "dry-run", ...) String dryRun) {
HashMap map = new HashMap();
map.put("--cluster", cluster); // ← Maps correctly
// ...
}Fixes Required
Fix 1: CommandUtil.listOptions() - Use Longest Option Name
Location: CommandUtil.java, lines 97-99
Current Code:
String[] names = o.value("names").asStringArray();
if (names.length != 0) {
options.put(names[0], a); // ← BUG: Uses first option (often short form)
}Problem: For @Option(names = { "-c", "--cluster" }), this puts "-c" in the map, which becomes parameter name "c" after removing dashes.
Proposed Fix:
String[] names = o.value("names").asStringArray();
if (names.length != 0) {
// Use the longest option name (typically the --long-form)
String longestName = names[0];
for (String name : names) {
if (name.length() > longestName.length()) {
longestName = name;
}
}
options.put(longestName, a);
}Result: For @Option(names = { "-c", "--cluster" }), this puts "--cluster" in the map.
Fix 2: McpServerCliAdapterProcessor.wrapCommand() - Use Option Name
Location: McpServerCliAdapterProcessor.java, lines 314-325
Current Code:
for (Entry<String, FieldInfo> entry : options.entrySet()) {
FieldInfo option = entry.getValue();
String optionName = option.name().toString(); // ← BUG: Uses field nameProposed Fix:
for (Entry<String, FieldInfo> entry : options.entrySet()) {
FieldInfo option = entry.getValue();
// Use the CLI option name from the map key (now the longest option after Fix 1)
String optionName = entry.getKey().replaceAll("^-*", "");Result: With both fixes, @Option(names = { "-c", "--cluster" }) becomes parameter name "cluster".
Impact
Affected Commands
All Picocli commands with options are affected by at least one of these bugs:
Both Bugs (field name mismatch + short option used)
@Option(names = { "-c", "--cluster" }) private String clusterNameOrId;- Current: Uses field name
"clusterNameOrId"✗ - After Fix 1 only: Uses
"-c"→"c"✗ - After both fixes: Uses
"--cluster"→"cluster"✓
- Current: Uses field name
Primary Bug Only (field name mismatch)
@Option(names = "--json") private boolean jsonOutput;- Current: Uses field name
"jsonOutput"✗ - After fixes: Uses
"--json"→"json"✓
- Current: Uses field name
Secondary Issue Only (short option preferred)
@Option(names = { "-n", "--namespace" }) private List<String> namespaces;- Current: Uses field name
"namespaces"(happens to work) ✓ - But if field matched option: Would use
"-n"→"n"✗ - After both fixes: Uses
"--namespace"→"namespace"✓
- Current: Uses field name
Workaround
Rename all Java fields to match their primary Picocli option names:
// Before (fails with MCP)
@Option(names = { "-c", "--cluster" })
private String clusterNameOrId;
// After (works with MCP)
@Option(names = { "-c", "--cluster" })
private String cluster;Test Case
Command
ops-tools namespace node-info --namespace tt2-eip-all --cluster eip-reflupMCP Tool Call (Current - Fails)
{
"tool": "namespace_namespacenodeinfocommand",
"arguments": {
"namespaces": "tt2-eip-all",
"clusterNameOrId": "eip-reflup" // ← Wrong parameter name
}
}MCP Tool Call (After Fix - Should Work)
{
"tool": "namespace_namespacenodeinfocommand",
"arguments": {
"namespaces": "tt2-eip-all",
"cluster": "eip-reflup" // ← Correct parameter name
}
}Additional Notes
Why Both Fixes Are Needed
Line 352 in McpServerCliAdapterProcessor.wrapCommand() already uses entry.getKey() correctly:
ResultHandle paramKey = tryBlock.load(entry.getKey());This proves that entry.getKey() is the right source for the CLI option name. However, the current implementation has two problems:
- Line 316 uses
option.name().toString()(field name) instead ofentry.getKey()(option name) - CommandUtil.listOptions() puts
names[0](first/shortest option) in the map instead of the longest option
Understanding FieldInfo.name()
The FieldInfo class is from Jandex (JBoss bytecode indexing library):
FieldInfo.name()returns aDotNamerepresenting the Java field identifieroption.name().toString()converts this to a String like"clusterNameOrId"- This is the Java field name, not the Picocli
@Option(names = ...)value
Why Use the Longest Option Name?
Picocli conventions typically use:
- Short options:
-c,-n,-v(single letter, less descriptive) - Long options:
--cluster,--namespace,--verbose(descriptive, self-documenting)
For MCP tool parameters, the long form is preferable because:
- More descriptive:
"cluster"vs"c" - Better API documentation
- Matches common CLI conventions
- More intuitive for users
CommandUtil.listOptions() Behavior
The method returns a Map<String, FieldInfo> where:
- Key: Currently the first CLI option name (e.g.,
"-c") - Value: The FieldInfo containing the Java field metadata
After Fix 1, the key will be the longest option name (e.g., "--cluster").
Severity
High - Affects all commands with options:
- Commands with mismatched field/option names fail completely
- Commands with short options get poor parameter names (e.g.,
"c"instead of"cluster")
Recommendations
- Fix both issues in a single PR for consistency
- Add unit tests to verify:
- MCP parameter names match the longest CLI option name
- Parameter names are properly stripped of leading dashes
- Field names are NOT used for parameter names
- Document the behavior: MCP tool parameters use the longest option name from
@Option(names = {...}) - Consider backward compatibility: This change improves parameter names but may break existing MCP clients that adapted to the buggy behavior
Testing Strategy
Test Case 1: Multiple Option Names
@Option(names = { "-c", "--cluster" })
private String clusterNameOrId;Expected MCP parameter: "cluster" (from "--cluster")
Test Case 2: Single Long Option
@Option(names = "--namespace")
private List<String> namespaces;Expected MCP parameter: "namespace"
Test Case 3: Field Name Matches Option
@Option(names = { "-d", "--dry-run" })
private boolean dryRun;Expected MCP parameter: "dry-run" (NOT "dryRun" from field name)
Test Case 4: Hyphenated Options
@Option(names = "--json-output")
private boolean jsonOutput;Expected MCP parameter: "json-output" (preserves hyphens)