github/copilot-sdk · error · IllegalArgumentException
Invalid mcp tool name
Error message
Invalid mcp tool name '<name>': tool names must match /^[a-zA-Z0-9_-]+$/ or be the wildcard '*'. (template: Invalid <kind> tool name '<name>': tool names must match /^[a-zA-Z0-9_-]+$/ or be the wildcard '*'.)
What it means
ToolSet names must match /^[a-zA-Z0-9_-]+$/ or be the wildcard "*". validateName throws this IllegalArgumentException when a name is non-empty but contains illegal characters (e.g. dots, colons, spaces, slashes), because such names cannot be matched against tool identifiers.
Solutions
- Sanitize the name to allowed characters (letters, digits, underscore, hyphen) before adding.
- Strip prefixes/qualifiers so only the bare tool name is passed (e.g. 'myserver_mytool' rather than 'my.server/mytool').
- Use the exact string "*" (no whitespace) when you intend to allow all tools.
- Validate names against /^[a-zA-Z0-9_-]+$/ in your config-loading code and fail with a clear message.
Example fix
// before
set.addMcp("github.com/tools/search"); // illegal characters
// after
set.addMcp("github-com-tools-search"); // sanitized, matches /^[a-zA-Z0-9_-]+$/ Defensive patterns
Strategy: validation
Validate before calling
static final Pattern OK = Pattern.compile("^[a-zA-Z0-9_-]+$");
if (name == null || (!name.equals("*") && !OK.matcher(name).matches()))
throw new IllegalArgumentException("illegal tool name: " + name); Type guard
static boolean isAllowedToolName(String n) {
return "*".equals(n) || (n != null && n.matches("^[a-zA-Z0-9_-]+$"));
} Try / catch
try { set.addMcp(rawName); } catch (IllegalArgumentException e) {
set.addMcp(rawName.replaceAll("[^a-zA-Z0-9_-]", "-")); // sanitized retry
} Prevention
- Sanitize names (replace dots/colons/slashes with hyphens) at config-load time.
- Never pass prefixed identifiers like 'server:tool' or 'a.b.c' into add* methods.
- Use the exact wildcard "*" without whitespace when allowing all tools.
When it happens
Trigger: Calling toolSet.addMcp("my.server.tool"), addBuiltIn("search docs"), addCustom("fs/read"), or any name with characters outside [a-zA-Z0-9_-]. Passing a prefixed identifier like "mcp:foo" instead of the bare name "foo".
Common situations: Passing MCP server identifiers with dots/colons (FQDNs, URIs) instead of the registered tool name; including namespace prefixes like 'server:tool'; names with spaces copied from documentation; accidentally passing the wildcard with surrounding whitespace.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- Invalid tool name ' ': tool names must match…
- Invalid mcp tool name: must not be null or empty.
- CliUrl is mutually exclusive with CliPath
- TcpConnectionToken must be a non-empty string
- Invalid value ' '. Expected 'inprocess', 'stdio', or unset.
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/ae50b25d094b32b2.
Report an issue: GitHub.
Appendix: source
Thrown at java/sdk/src/main/java/com/github/copilot/rpc/ToolSet.java:118
* @return this {@code ToolSet} for chaining
* @throws IllegalArgumentException
* if toolName is null, empty, or contains invalid characters
*/
public ToolSet addMcp(String toolName) {
validateName("mcp", toolName);
add("mcp:" + toolName);
return this;
}
private static void validateName(String kind, String name) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("Invalid " + kind + " tool name: must not be null or empty.");
}
if ("*".equals(name)) {
return;
}
if (!VALID_TOOL_NAME.matcher(name).matches()) {
throw new IllegalArgumentException("Invalid " + kind + " tool name '" + name
+ "': tool names must match /^[a-zA-Z0-9_-]+$/ or be the wildcard '*'.");
}
}
}
View on GitHub (pinned to cd8cf15dc3)