Source code for oas2mcp.utils.names
"""Naming helpers for normalized catalogs and MCP preparation.
Purpose:
Create stable slugs, tool names, and resource URIs from normalized OpenAPI
metadata.
Design:
- Keep naming deterministic.
- Prefer operation identifiers when available.
- Fall back to method + path when ``operationId`` is missing.
Examples:
.. code-block:: python
slug = make_operation_slug(operation)
tool_name = make_tool_name(catalog_name="petstore", operation=operation)
"""
from __future__ import annotations
import re
from oas2mcp.models.normalized import ApiOperation
_NON_ALNUM_PATTERN = re.compile(r"[^a-z0-9]+")
_DUP_DASH_PATTERN = re.compile(r"-+")
[docs]
def slugify(value: str) -> str:
"""Convert a string into a simple slug.
Args:
value: The input string.
Returns:
A lowercase slug.
Raises:
None.
Examples:
.. code-block:: python
assert slugify("Swagger Petstore - OpenAPI 3.0") == "swagger-petstore-openapi-3-0"
"""
lowered = value.strip().lower()
replaced = _NON_ALNUM_PATTERN.sub("-", lowered)
collapsed = _DUP_DASH_PATTERN.sub("-", replaced).strip("-")
return collapsed or "item"
[docs]
def make_catalog_slug(catalog_name: str) -> str:
"""Create a stable catalog slug.
Args:
catalog_name: The catalog display name.
Returns:
The catalog slug.
Raises:
None.
Examples:
.. code-block:: python
slug = make_catalog_slug("Swagger Petstore - OpenAPI 3.0")
"""
return slugify(catalog_name)
[docs]
def make_tag_slug(tag_name: str) -> str:
"""Create a stable tag slug.
Args:
tag_name: The tag name.
Returns:
The tag slug.
Raises:
None.
Examples:
.. code-block:: python
slug = make_tag_slug("pet")
"""
return slugify(tag_name)
[docs]
def make_operation_slug(operation: ApiOperation) -> str:
"""Create a stable operation slug.
Args:
operation: The normalized API operation.
Returns:
The operation slug.
Raises:
None.
Examples:
.. code-block:: python
slug = make_operation_slug(operation)
"""
if operation.operation_id:
return slugify(operation.operation_id)
path_bits = (
operation.path.strip("/").replace("/", "-").replace("{", "").replace("}", "")
)
return slugify(f"{operation.method.lower()}-{path_bits}")
[docs]
def make_resource_uri(
*,
catalog_name: str,
resource_kind: str,
identifier: str,
) -> str:
"""Create a suggested MCP-style resource URI.
Args:
catalog_name: The API catalog name.
resource_kind: The logical resource kind, such as ``operation`` or
``schema``.
identifier: The resource identifier or slug.
Returns:
A suggested resource URI.
Raises:
None.
Examples:
.. code-block:: python
uri = make_resource_uri(
catalog_name="Petstore",
resource_kind="operation",
identifier="get-pet-by-id",
)
"""
return f"openapi://{make_catalog_slug(catalog_name)}/{slugify(resource_kind)}/{slugify(identifier)}"
[docs]
def make_operation_resource_uri(*, catalog_name: str, operation: ApiOperation) -> str:
"""Create a resource URI or URI template for one operation.
Args:
catalog_name: The API catalog name.
operation: The normalized API operation.
Returns:
A stable URI for read-only resources or a URI template for parameterized
read operations.
"""
catalog_slug = make_catalog_slug(catalog_name)
path_segments: list[str] = []
for raw_segment in operation.path.strip("/").split("/"):
if not raw_segment:
continue
if raw_segment.startswith("{") and raw_segment.endswith("}"):
path_segments.append(raw_segment)
continue
path_segments.append(slugify(raw_segment))
base_uri = f"openapi://{catalog_slug}"
if path_segments:
base_uri += "/" + "/".join(path_segments)
query_parameters = sorted(
parameter.name
for parameter in operation.parameters
if parameter.location == "query"
)
if query_parameters:
base_uri += "{?" + ",".join(query_parameters) + "}"
return base_uri