{"record":{"id":"4fb012c2255c71e6","repo":"windmill-labs/windmill","slug":"duplicate-mcp-tool-name-s-join-duplicates","errorCode":null,"errorMessage":"duplicate MCP tool name(s): {', '.join(duplicates)}","messagePattern":"duplicate MCP tool name\\(s\\): (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/generate_mcp_endpoints_tools/generate_mcp_tools.py","lineNumber":470,"sourceCode":"                    'path': path,\n                    'method': method.upper(),\n                    'parameters': operation.get('parameters', []),\n                    'requestBody': operation.get('requestBody'),\n                    'required_fields': operation.get('x-mcp-required-fields', []),\n                    'include_fields': operation.get('x-mcp-tool-include-fields'),\n                    'opaque_fields': operation.get('x-mcp-tool-opaque-fields'),\n                    'include_query_params': operation.get('x-mcp-tool-include-query-params'),\n                }\n                tools.append(tool)\n\n    # A tool name used to be an operationId, which OpenAPI already keeps unique.\n    # `x-mcp-tool-name` gives that up, and a duplicate would be silent: a token's\n    # `mcp:endpoints:<name>` resolves by first match, so which endpoint it reaches\n    # would depend on the order of this file.\n    names = [t['name'] for t in tools]\n    duplicates = sorted({n for n in names if names.count(n) > 1})\n    if duplicates:\n        raise ValueError(f\"duplicate MCP tool name(s): {', '.join(duplicates)}\")\n\n    return tools\n\ndef generate_typescript_code(tools: List[Dict[str, Any]], spec: Dict[str, Any], base_path: str = \"\") -> str:\n    \"\"\"Generate TypeScript code with MCP endpoint tools.\n\n    This catalogue only feeds the frontend's MCP scope picker, which reads names and\n    methods.\n    \"\"\"\n    if not tools:\n        return \"\"\"// Auto-generated MCP tools from OpenAPI specification\n// This file is generated by generate_mcp_tools.py - DO NOT EDIT MANUALLY\n\nexport interface EndpointTool {\n    name: string;\n    description: string;\n    instructions: string;\n    path: string;","sourceCodeStart":452,"sourceCodeEnd":488,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/backend/generate_mcp_endpoints_tools/generate_mcp_tools.py#L452-L488","documentation":"backend/generate_mcp_endpoints_tools/generate_mcp_tools.py's find_mcp_tools collects MCP tool names derived from spec endpoints via x-mcp-tool-name. Tool names must be unique because a token's mcp:endpoints:<name> resolves by first match — a duplicate would silently route to whichever tool is generated first, so the generator raises ValueError listing the duplicated names.","triggerScenarios":"Two or more endpoints in the OpenAPI spec declare the same x-mcp-tool-name (or derive the same name), so the tools list contains repeated 'name' entries and the sorted-duplicates check trips.","commonSituations":"Copy-pasting an endpoint block in the spec and forgetting to change x-mcp-tool-name; adding a v2 endpoint alongside v1 with the same tool name; renaming an operation but leaving a stale x-mcp-tool-name collision with another route.","solutions":["Identify the duplicated names from the error message and grep the spec for that x-mcp-tool-name","Rename one endpoint's x-mcp-tool-name so every tool name is unique","If the name is derived rather than explicit, adjust the path/operation so generated names differ","Regenerate the MCP tools (run the generator's main) and confirm no ValueError"],"exampleFix":"# before (spec)\nx-mcp-tool-name: get-job\n# on two different endpoints\n\n# after\nx-mcp-tool-name: get-job          # first endpoint\nx-mcp-tool-name: get-job-result   # second endpoint","handlingStrategy":"validation","validationCode":"# validate tool-name uniqueness in the spec before running the generator\nimport yaml, collections\nspec = yaml.safe_load(open('spec.yaml'))\nnames = [op.get('x-mcp-tool-name') for op in spec['paths'].values() if op.get('x-mcp-tool-name')]\ndups = [n for n, c in collections.Counter(names).items() if c > 1]\nassert not dups, f\"duplicate x-mcp-tool-name: {dups}\"","typeGuard":"def has_unique_tool_names(tools: list[dict]) -> bool:\n    names = [t['name'] for t in tools]\n    return len(names) == len(set(names))","tryCatchPattern":"try:\n    tools = find_mcp_tools(spec)\nexcept ValueError as e:\n    if str(e).startswith('duplicate MCP tool name'):\n        sys.exit(f\"Fix the spec: {e}\")\n    raise","preventionTips":["Grep the spec for x-mcp-tool-name before adding new endpoints","Adopt a naming convention encoding the operation/resource in the tool name","Run the generator in CI so collisions fail fast","Review spec diff for copy-pasted endpoint blocks"],"tags":["python","codegen","openapi","naming-collision","mcp"],"backgroundTag":"duplicate-name-collision","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}