{"record":{"id":"142619208f18a395","repo":"microsoft/aspire","slug":"toolbox-name-contains-duplicate-tool-names-string-join","errorCode":null,"errorMessage":"Toolbox '{name}' contains duplicate tool names: {string.Join(\", \", duplicateToolNames)}.","messagePattern":"Toolbox '(.+?)' contains duplicate tool names: (.+?)\\.","errorType":"validation","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.Foundry/Toolbox/FoundryToolboxReconciler.cs","lineNumber":76,"sourceCode":"        ArgumentException.ThrowIfNullOrWhiteSpace(description);\n        ArgumentNullException.ThrowIfNull(tools);\n        ArgumentNullException.ThrowIfNull(metadata);\n\n        if (tools.Count == 0)\n        {\n            throw new InvalidOperationException($\"Toolbox '{name}' must contain at least one tool.\");\n        }\n\n        var duplicateToolNames = tools\n            .GroupBy(tool => tool.Name, StringComparer.Ordinal)\n            .Where(group => group.Count() > 1)\n            .Select(group => group.Key)\n            .Order(StringComparer.Ordinal)\n            .ToArray();\n\n        if (duplicateToolNames.Length > 0)\n        {\n            throw new InvalidOperationException(\n                $\"Toolbox '{name}' contains duplicate tool names: {string.Join(\", \", duplicateToolNames)}.\");\n        }\n\n        var duplicateMcpServerLabels = tools\n            .Where(tool => tool.McpServerLabel is not null)\n            .GroupBy(tool => tool.McpServerLabel!, StringComparer.Ordinal)\n            .Where(group => group.Count() > 1)\n            .Select(group => group.Key)\n            .Order(StringComparer.Ordinal)\n            .ToArray();\n        if (duplicateMcpServerLabels.Length > 0)\n        {\n            throw new InvalidOperationException(\n                $\"Toolbox '{name}' contains duplicate MCP server labels: {string.Join(\", \", duplicateMcpServerLabels)}.\");\n        }\n\n        var maximumUserMetadataEntries = MaximumMetadataEntries - s_reservedMetadataKeys.Length;\n        if (metadata.Count > maximumUserMetadataEntries)","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.Foundry/Toolbox/FoundryToolboxReconciler.cs#L58-L94","documentation":"Thrown by FoundryToolboxReconciler.Create when two or more tool definitions in the collection share the same Name (compared with StringComparer.Ordinal). Tool names must be unique within a Toolbox because they become the identifiers the MCP clients invoke; duplicates would be ambiguous. The message lists the offending names.","triggerScenarios":"Calling Create with a tools collection containing two entries whose Name properties are identical — e.g. the same WithMcpTool name used twice, or tools aggregated from multiple sources that both register a tool named 'search'.","commonSituations":"Merging tool lists from config plus code where a name collides; loop that adds the same tool per-resource without suffixing names; copy-paste duplication in the app model; two MCP servers both exposing a tool with the same name added under one Toolbox.","solutions":["Rename one of the duplicate tools so every tool name in the Toolbox is unique.","If tools come from multiple MCP servers with colliding names, prefix names with the server label or scope.","Deduplicate before calling Create (e.g. DistinctBy(t => t.Name, StringComparer.Ordinal)) and log dropped duplicates."],"exampleFix":"// before\nbuilder.WithMcpTool(\"search\", \"https://a.example.com/mcp\");\nbuilder.WithMcpTool(\"search\", \"https://b.example.com/mcp\");\n// after\nbuilder.WithMcpTool(\"a-search\", \"https://a.example.com/mcp\");\nbuilder.WithMcpTool(\"b-search\", \"https://b.example.com/mcp\");","handlingStrategy":"validation","validationCode":"var dupes = tools.GroupBy(t => t.Name, StringComparer.Ordinal).Where(g => g.Count() > 1).Select(g => g.Key).ToArray();\nif (dupes.Length > 0)\n{\n    throw new InvalidOperationException($\"Duplicate tool names before Toolbox creation: {string.Join(\", \", dupes)}\");\n}","typeGuard":"static bool HasUniqueToolNames(IEnumerable<FoundryToolDefinition> tools) =>\n    tools.Select(t => t.Name).Distinct(StringComparer.Ordinal).Count() == tools.Count();","tryCatchPattern":"try\n{\n    var toolbox = FoundryToolboxReconciler.Create(name, description, tools, metadata);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"duplicate tool names\"))\n{\n    logger.LogError(ex, \"Toolbox {Name} has duplicate tool names; rename one of the registrations.\", name);\n    throw;\n}","preventionTips":["Prefix tool names with their source MCP server label when aggregating from multiple servers.","Add a unit test asserting tool-name uniqueness in the app model.","Deduplicate with DistinctBy(t => t.Name) and log dropped duplicates when merging tool lists."],"tags":["validation","duplicate-keys","foundry-toolbox"],"backgroundTag":"conflicting-config-options","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}