Skip to content

Discovery tools return the wrong operation when an operationId exists in more than one namespace #2

Description

@basraayman

MCP client

Cursor

Command or phase

Other

Namespace affected

Multiple namespaces

Environment

_find_operation in src/generators/tool_generator.py looks up an operation by name with no namespace scoping:

def _find_operation(self, operation_id):
    for op in self.operations:
        if op.registered_name == operation_id:
            return op   # first match across ALL namespaces
    raise KeyError(...)

self.operations is the flat list of every namespace's operations, so this returns the first match regardless of which namespace it belongs to. get_operation_schema then caches the result keyed by operation_id alone, so the first answer is used for the life of the process.

resolve_collisions runs per file. extract_operations is called once per namespace YAML in load_operations_from_yamls, so it only de-duplicates operationIds within a single namespace. If the same bare operationId appears in two namespace specs, both keep the plain operationId as their registered_name, and the global first-match decides the winner. Files load in sorted order, so the alphabetically-earlier namespace wins.

  • getOperationSchema
  • getCodeSample
  • getOperationPermissions

All call _find_operation so all are affected. This will never show up in a single-namespace test.

The SchemaResolver has a related problem for shared model names. combined_schemas is built by update() across all namespace files (last writer wins), and $ref is cached by bare ref name, so a component schema defined in two namespaces resolves to whichever file loaded last. That can hand the model the wrong body shape for a common name across specs.

Suggestions:

  • Key _find_operation and the schema cache on (namespace, operation_id) rather than operation_id alone.
  • Scope SchemaResolver and its $ref cache per namespace as well, so a shared model name in one spec cannot overwrite another.

Steps to reproduce

  1. Create an artifacts directory with two namespace specs that share one operationId. clustermgmt sorts before vmm, so clustermgmt will win.
clustermgmt-v4.0-all-documentation.yaml:
openapi: 3.0.0
paths:
  /clustermgmt/v4.0/config/disks/{extId}:
    get:
      operationId: getDiskById
      summary: Get a cluster disk by ID

vmm-v4.0-all-documentation.yaml:
openapi: 3.0.0
paths:
  /vmm/v4.0/ahv/config/vms/{vmExtId}/disks/{extId}:
    get:
      operationId: getDiskById
      summary: Get a VM disk by ID

  1. Point the server at that directory and ask for the schema of the vmm operation:
from src.config import Settings
from src.server import build_runtime_dispatcher

settings = Settings(pc_host=None, artifacts_dir=<dir above>, default_artifacts_dir=<empty dir>)
dispatcher = build_runtime_dispatcher(settings)

print(dispatcher.call_tool("getOperationSchema", {"operation": "getDiskById"}).payload)

Expected behavior

For a vmm disk lookup, the schema shows path /vmm/v4.0/ahv/config/vms/{vmExtId}/disks/{extId} with both vmExtId and extId path parameters.

Actual behavior

The payload shows the clustermgmt operation instead: path /clustermgmt/v4.0/config/disks/{extId}, summary "Get a cluster disk by ID", and only the extId parameter. _find_operation returned the first getDiskById in the flat list, which is the alphabetically earlier clustermgmt spec.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions