github/copilot-sdk · error · ArgumentException

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

ToolSet.ValidateName also enforces the character set for tool names: a name must match ^[a-zA-Z0-9_-]+$ or be the literal wildcard "*". Names containing dots, slashes, spaces, colons, or other characters are rejected with ArgumentException. This normalization keeps tool names valid across MCP and filter semantics.

Solutions

  1. Sanitize the name to the allowed charset: replace invalid characters with '_' or '-' before calling Add*.
  2. Strip server/namespace prefixes (e.g. everything before '/') so only the base tool name is registered.
  3. If wildcard semantics are intended, pass the literal "*" instead of a pattern like '*.write'.
  4. Catch ArgumentException and report which name failed the pattern ^[a-zA-Z0-9_-]+$.

Example fix

// before
toolSet.AddMcp("github.com/org/repo:create_issue");

// after
var safe = new string(name.Select(c => char.IsAsciiLetterOrDigit(c) || c is '_' or '-' ? c : '_').ToArray());
toolSet.AddMcp(safe);
Defensive patterns

Strategy: validation

Validate before calling

if (name != "*" && !System.Text.RegularExpressions.Regex.IsMatch(name ?? "", "^[a-zA-Z0-9_-]+$"))
    throw new ArgumentException($"Invalid tool name '{name}'", nameof(name));

Type guard

static bool IsValidToolName(string? name) =>
    name == "*" || (name != null && System.Text.RegularExpressions.Regex.IsMatch(name, "^[a-zA-Z0-9_-]+$(?<=^[a-zA-Z0-9_-]+$)")) is true && System.Text.RegularExpressions.Regex.IsMatch(name, "^[a-zA-Z0-9_-]+$");

Try / catch

try { toolSet.AddMcp(name); }
catch (ArgumentException ex) when (ex.Message.Contains("tool names must match"))
{ logger.LogError("Tool name '{Name}' has invalid characters", name); }

Prevention

When it happens

Trigger: Adding a tool whose name contains characters outside [a-zA-Z0-9_-], e.g. AddMcp("github.com/server/create_issue") or AddCustom("my tool"), unless the exact name is "*".

Common situations: MCP server tool ids arriving with server prefixes or dots; converting display labels (with spaces/slashes) into tool names; generating names from URLs or file paths.

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/c96c296bfc746c4e. Report an issue: GitHub.

Appendix: source

Thrown at dotnet/src/ToolSet.cs:114

        Add($"mcp:{toolName}");
        return this;
    }

    private static void ValidateName(string kind, string name)
    {
        if (string.IsNullOrEmpty(name))
        {
            throw new ArgumentException(
                $"Invalid {kind} tool name: must not be null or empty.",
                nameof(name));
        }
        if (name == "*")
        {
            return;
        }
        if (!s_validToolName.IsMatch(name))
        {
            throw new ArgumentException(
                $"Invalid {kind} tool name '{name}': tool names must match /^[a-zA-Z0-9_-]+$/ " +
                "or be the wildcard '*'.",
                nameof(name));
        }
    }
}

/// <summary>
/// Curated sets of built-in tool names for common scenarios. Each constant is
/// meant to be passed to <see cref="ToolSet.AddBuiltIn(IEnumerable{string})"/>.
/// </summary>
public static class BuiltInTools
{
    /// <summary>
    /// Built-in tools that operate only within the bounds of a single session
    /// — no host filesystem access outside the session, no cross-session
    /// state, no host environment access, no network. Safe to enable in
    /// <see cref="CopilotClientMode.Empty"/> scenarios (e.g. multi-tenant

View on GitHub (pinned to cd8cf15dc3)