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

  1. Sanitize the tool name to only [a-zA-Z0-9_-] before registering (e.g. replace invalid chars with "_")
  2. Use the wildcard "*" only when you intentionally mean all tools
  3. 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

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


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)