github/copilot-sdk · error
Invalid tool name ' ': tool names must match…
Error message
Invalid ${kind} tool name '${name}': tool names must match /^[a-zA-Z0-9_-]+$/ or be the wildcard '*'. What it means
Thrown by validateName in toolSet.ts when registering a builtin, mcp, or custom tool whose name is neither the wildcard "*" nor matches /^[a-zA-Z0-9_-]+$/. The toolset enforces a strict identifier charset so tool names can be safely referenced and dispatched.
Solutions
- Sanitize the tool name to only [a-zA-Z0-9_-] before registering (e.g. replace invalid chars with "_")
- Use the wildcard "*" only when you intentionally mean all tools
- Fix the name string so it matches /^[a-zA-Z0-9_-]+$/
Example fix
// before
toolSet.addMcp("github.com:search-issues"); // invalid ':' and '.'
// after
const safe = "github.com:search-issues".replace(/[^a-zA-Z0-9_-]/g, "_"); // github_com_search-issues
toolSet.addMcp(safe); Defensive patterns
Strategy: validation
Validate before calling
const VALID_TOOL_NAME = /^[a-zA-Z0-9_-]+$/;
if (name !== "*" && !VALID_TOOL_NAME.test(name)) {
throw new Error(`Invalid tool name before registration: ${name}`);
} Type guard
function isValidToolName(name) {
return name === "*" || /^[a-zA-Z0-9_-]+$/.test(name);
} Try / catch
try {
toolSet.addMcp(rawName);
} catch (err) {
if (String(err?.message).startsWith("Invalid ")) {
toolSet.addMcp(rawName.replace(/[^a-zA-Z0-9_-]/g, "_"));
} else throw err;
} Prevention
- Sanitize names derived from MCP server ids, paths, or URLs before registration
- Validate tool names in CI with a unit test against /^[a-zA-Z0-9_-]+$/
- Reserve "*" for wildcard semantics only
When it happens
Trigger: Calling addBuiltIn/addCustom/addMcp with a name containing spaces, dots, slashes, colons, or non-ASCII characters; passing a fully-qualified MCP name like "server.tool" or a path-like name; passing an empty string or undefined coerced to a string.
Common situations: Registering MCP tools with vendor-prefixed names containing dots or colons; deriving tool names from file paths or URLs; migrating tool sets that previously allowed dotted names.
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 mcp tool name
- Invalid mcp tool name: must not be null or empty.
- Invalid tool name ' ': tool names must match…
- invalid tool name: must not be empty
- invalid tool name : tool names must match…
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/c83f75e353dae55b.
Report an issue: GitHub.
Appendix: source
Thrown at nodejs/src/toolSet.ts:25
* source-qualified filter patterns (`builtin:*`, `mcp:<name>`, `custom:*`, etc.).
*
* See plan: client-level Mode = "empty" with explicit tool selection.
*/
/**
* Tool name character set enforced by the runtime at every registration
* boundary. Mirrors the runtime's `VALID_TOOL_NAME_REGEX`. Used to validate
* names passed to the `ToolSet` builder so misuse is caught at the SDK
* boundary with a better error than the runtime would produce.
*/
const VALID_TOOL_NAME = /^[a-zA-Z0-9_-]+$/;
function validateName(kind: "builtin" | "mcp" | "custom", name: string): void {
if (name === "*") {
return;
}
if (!VALID_TOOL_NAME.test(name)) {
throw new Error(
`Invalid ${kind} tool name '${name}': tool names must match /^[a-zA-Z0-9_-]+$/ ` +
`or be the wildcard '*'.`
);
}
}
/**
* Builder that produces a list of source-qualified tool filter strings for
* {@link SessionConfigBase.availableTools}.
*
* Tools are classified by the runtime at registration time (not from name
* parsing), so `addBuiltIn("foo")` matches only tools the runtime registered
* as built-in, even if an MCP server or custom-agent extension happens to
* register a tool with the same wire name.
*
* @example
* ```typescript
* const tools = new ToolSet()View on GitHub (pinned to cd8cf15dc3)