windmill-labs/windmill · error · ValueError

duplicate MCP tool name(s)

Error message

duplicate MCP tool name(s): {', '.join(duplicates)}

What it means

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.

Solutions

  1. Identify the duplicated names from the error message and grep the spec for that x-mcp-tool-name
  2. Rename one endpoint's x-mcp-tool-name so every tool name is unique
  3. If the name is derived rather than explicit, adjust the path/operation so generated names differ
  4. Regenerate the MCP tools (run the generator's main) and confirm no ValueError

Example fix

# before (spec)
x-mcp-tool-name: get-job
# on two different endpoints

# after
x-mcp-tool-name: get-job          # first endpoint
x-mcp-tool-name: get-job-result   # second endpoint
Defensive patterns

Strategy: validation

Validate before calling

# validate tool-name uniqueness in the spec before running the generator
import yaml, collections
spec = yaml.safe_load(open('spec.yaml'))
names = [op.get('x-mcp-tool-name') for op in spec['paths'].values() if op.get('x-mcp-tool-name')]
dups = [n for n, c in collections.Counter(names).items() if c > 1]
assert not dups, f"duplicate x-mcp-tool-name: {dups}"

Type guard

def has_unique_tool_names(tools: list[dict]) -> bool:
    names = [t['name'] for t in tools]
    return len(names) == len(set(names))

Try / catch

try:
    tools = find_mcp_tools(spec)
except ValueError as e:
    if str(e).startswith('duplicate MCP tool name'):
        sys.exit(f"Fix the spec: {e}")
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/4fb012c2255c71e6. Report an issue: GitHub.

Appendix: source

Thrown at backend/generate_mcp_endpoints_tools/generate_mcp_tools.py:470

                    'path': path,
                    'method': method.upper(),
                    'parameters': operation.get('parameters', []),
                    'requestBody': operation.get('requestBody'),
                    'required_fields': operation.get('x-mcp-required-fields', []),
                    'include_fields': operation.get('x-mcp-tool-include-fields'),
                    'opaque_fields': operation.get('x-mcp-tool-opaque-fields'),
                    'include_query_params': operation.get('x-mcp-tool-include-query-params'),
                }
                tools.append(tool)

    # A tool name used to be an operationId, which OpenAPI already keeps unique.
    # `x-mcp-tool-name` gives that up, and a duplicate would be silent: a token's
    # `mcp:endpoints:<name>` resolves by first match, so which endpoint it reaches
    # would depend on the order of this file.
    names = [t['name'] for t in tools]
    duplicates = sorted({n for n in names if names.count(n) > 1})
    if duplicates:
        raise ValueError(f"duplicate MCP tool name(s): {', '.join(duplicates)}")

    return tools

def generate_typescript_code(tools: List[Dict[str, Any]], spec: Dict[str, Any], base_path: str = "") -> str:
    """Generate TypeScript code with MCP endpoint tools.

    This catalogue only feeds the frontend's MCP scope picker, which reads names and
    methods.
    """
    if not tools:
        return """// Auto-generated MCP tools from OpenAPI specification
// This file is generated by generate_mcp_tools.py - DO NOT EDIT MANUALLY

export interface EndpointTool {
    name: string;
    description: string;
    instructions: string;
    path: string;

View on GitHub (pinned to e474e8803c)