Skip to main content
Glama

get_field_info

Retrieve field metadata including data type, units, and discrete value mappings for filtering and analysis in EMS flight databases.

Instructions

Get field metadata including type, units, and discrete value mappings.

Essential for discrete fields: shows numeric code-to-label mappings needed for filtering. String labels in filters are auto-resolved, but use this to verify available values.

Args: ems_system_id: EMS system ID. database_id: Database ID or name (e.g. "FDW Flights"). field_id: Field reference: [N] number from find_fields, field name (e.g. "Takeoff Airport Name"), or bracket-encoded ID.

Returns: Field details with discrete value mappings if applicable.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
ems_system_idYes
database_idYes
field_idYes

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • Main get_field_info tool handler with @mcp.tool decorator. Implements field metadata retrieval including type, units, and discrete value mappings. Resolves database and field references, uses caching, and formats output for display.
    @mcp.tool
    async def get_field_info(
        ems_system_id: int,
        database_id: str,
        field_id: str | int,
    ) -> str:
        """Get field metadata including type, units, and discrete value mappings.
    
        Essential for discrete fields: shows numeric code-to-label mappings needed
        for filtering. String labels in filters are auto-resolved, but use this to
        verify available values.
    
        Args:
            ems_system_id: EMS system ID.
            database_id: Database ID or name (e.g. "FDW Flights").
            field_id: Field reference: [N] number from find_fields, field name
                (e.g. "Takeoff Airport Name"), or bracket-encoded ID.
    
        Returns:
            Field details with discrete value mappings if applicable.
        """
        # Resolve database name -> ID
        try:
            database_id = await _resolve_database_id(database_id, ems_system_id)
        except ValueError as e:
            return f"Error resolving database: {e}"
    
        # Resolve field reference -> opaque ID
        try:
            field_id = await _resolve_field_id(field_id, ems_system_id, database_id)
        except (ValueError, EMSAPIError) as e:
            return f"Error resolving field: {e}"
    
        client = get_client()
    
        cache_key = make_cache_key("field_info", ems_system_id, database_id, field_id)
        cached = await field_cache.get(cache_key)
        if cached is not None:
            logger.debug("Using cached field info: %s", cache_key)
            return _format_field_info(cached)
    
        try:
            encoded_field_id = urllib.parse.quote(field_id, safe="")
            path = (
                f"/api/v2/ems-systems/{ems_system_id}/databases/{database_id}/fields/{encoded_field_id}"
            )
    
            field = await client.get(path)
            await field_cache.set(cache_key, field)
            return _format_field_info(field)
        except EMSNotFoundError:
            return (
                "Error: Field not found. Verify field_id is correct. "
                "Use find_fields to find valid field IDs."
            )
        except EMSAPIError as e:
            return f"Error getting field info: {e.message}"
  • _format_field_info helper function that formats field metadata for display. Handles field name, type, units, description, and discrete values (with limiting for large sets). Returns formatted string output.
    def _format_field_info(field: dict[str, Any]) -> str:
        """Format detailed field information for display."""
        lines = []
    
        field_name = field.get("name", "Unknown")
        field_id = field.get("id", "?")
        field_type = field.get("type", "unknown")
    
        lines.append(f"Field: {field_name}")
        lines.append(f"Type: {field_type}")
    
        units = field.get("units")
        if units:
            lines.append(f"Units: {units}")
    
        description = field.get("description")
        if description:
            lines.append(f"Description: {description}")
    
        lines.append(f"\nField ID: {field_id}")
    
        # Handle discrete values
        discrete_values = field.get("discreteValues")
        if discrete_values:
            # Normalize dict format {"code": "label"} to list format [{"value": code, "label": label}]
            if isinstance(discrete_values, dict):
                discrete_values = [{"value": k, "label": v} for k, v in discrete_values.items()]
    
            lines.append(f"\nDiscrete Values ({len(discrete_values)}):")
            # Limit display for large value sets
            display_count = min(len(discrete_values), 50)
            for dv in discrete_values[:display_count]:
                value = dv.get("value", "?")
                label = dv.get("label", "Unknown")
                lines.append(f"  {value}: {label}")
            if len(discrete_values) > display_count:
                lines.append(f"  ... and {len(discrete_values) - display_count} more values")
    
        return "\n".join(lines)
  • _resolve_field_id helper that resolves field references to opaque field IDs. Supports integer references (result store lookup), bracket-encoded IDs, and human-readable names via API search.
    async def _resolve_field_id(
        field_ref: str | int,
        ems_system_id: int,
        database_id: str,
    ) -> str:
        """Resolve a field reference to an opaque field ID.
    
        Resolution order:
        1. Integer or digit string -> look up in result store
        2. Bracket-encoded string (starts with ``[``) -> pass through
        3. Human-readable name -> search field API, exact or single match
    
        Args:
            field_ref: A result store reference number, bracket-encoded ID, or
                human-readable field name.
            ems_system_id: The EMS system ID for API lookups.
            database_id: The database ID for API lookups.
    
        Returns:
            The resolved opaque field ID string.
    
        Raises:
            ValueError: If the reference cannot be resolved.
        """
        # 1. Integer or digit string -> result store lookup
        if isinstance(field_ref, int) or (isinstance(field_ref, str) and field_ref.strip().isdigit()):
            ref_num = int(field_ref) if isinstance(field_ref, str) else field_ref
            entry = _get_stored_result(ref_num)
            if entry is not None:
                if entry.get("type") == "analytic":
                    raise ValueError(
                        f"Reference [{ref_num}] ('{entry['name']}') is an analytic parameter, "
                        "not a database field. Use it with query_flight_analytics, "
                        "or use find_fields to find database field references."
                    )
                return entry["id"]
            raise ValueError(
                f"Reference [{ref_num}] not found in result store. "
                "Re-run find_fields to get fresh references."
            )
    
        if not isinstance(field_ref, str) or not field_ref.strip():
            raise ValueError(f"Invalid field reference: {field_ref!r}")
    
        field_ref = field_ref.strip()
    
        # 2. Bracket-encoded string -> pass through
        if field_ref.startswith("["):
            return field_ref
    
        # 3. Human-readable name -> search via API
        cache_key = make_cache_key("field_resolve", ems_system_id, database_id, field_ref.lower())
        cached = await field_cache.get(cache_key)
        if cached is not None:
            return cached
    
        client = get_client()
    
        # Entity-type databases don't support the field search endpoint (405);
        # fall back to BFS traversal of field groups.
        if _is_entity_type_database(database_id):
            matches, _ = await _recursive_field_search(
                client, ems_system_id, database_id,
                search_text=field_ref,
                max_depth=10, max_results=50, max_groups=50,
            )
            search_results = matches
        else:
            path = f"/api/v2/ems-systems/{ems_system_id}/databases/{database_id}/fields"
            params = {"text": field_ref}
            search_results = await client.get(path, params=params)
    
        if not search_results:
            raise ValueError(
                f"Field not found: '{field_ref}'. "
                "Use find_fields to discover valid field names."
            )
    
        # Try exact name match (case-insensitive)
        exact_matches = [
            f for f in search_results
            if f.get("name", "").lower() == field_ref.lower()
        ]
        if len(exact_matches) == 1:
            resolved_id = exact_matches[0]["id"]
            await field_cache.set(cache_key, resolved_id)
            return resolved_id
    
        # Single result total -> use it
        if len(search_results) == 1:
            resolved_id = search_results[0]["id"]
  • _resolve_database_id helper that resolves database names to opaque database IDs. Supports bracket-encoded IDs and human-readable names via cached database groups API lookup.
    async def _resolve_database_id(
        database_ref: str,
        ems_system_id: int,
    ) -> str:
        """Resolve a database name to an opaque database ID.
    
        Resolution order:
        1. Bracket-encoded string (starts with ``[``) -> pass through
        2. Human-readable name -> look up via database groups API
    
        The first call fetches root database groups + one level deep and caches
        the full name-to-ID mapping.
    
        Args:
            database_ref: A bracket-encoded database ID or human-readable name.
            ems_system_id: The EMS system ID for API lookups.
    
        Returns:
            The resolved opaque database ID string.
    
        Raises:
            ValueError: If the name cannot be resolved.
        """
        if not database_ref or not database_ref.strip():
            raise ValueError("database_id cannot be empty.")
    
        database_ref = database_ref.strip()
    
        # 1. Bracket-encoded -> pass through
        if database_ref.startswith("["):
            return database_ref
    
        # 2. Name -> look up in cached mapping
        cache_key = make_cache_key("database_name_map", ems_system_id)
        name_map: dict[str, str] | None = await database_cache.get(cache_key)
    
        if name_map is None:
            # Build the name -> ID mapping from root + one level of subgroups
            client = get_client()
            name_map = {}
    
            try:
                root = await client.get(f"/api/v2/ems-systems/{ems_system_id}/database-groups")
            except (EMSAPIError, EMSNotFoundError) as e:
                raise ValueError(f"Failed to fetch database groups: {e}") from e
    
            # Collect databases at root
            for db in root.get("databases", []):
                db_id = db.get("id", "")
                for name_key in ("name", "pluralName", "singularName"):
                    db_name = db.get(name_key)
                    if db_name:
                        name_map[db_name.lower()] = db_id
    
            # Fetch one level of subgroups
            for group in root.get("groups", []):
                group_id = group.get("id")
                if not group_id:
                    continue
                try:
                    sub = await client.get(
                        f"/api/v2/ems-systems/{ems_system_id}/database-groups?groupId={group_id}"
                    )
                    for db in sub.get("databases", []):
                        db_id = db.get("id", "")
                        for name_key in ("name", "pluralName", "singularName"):
                            db_name = db.get(name_key)
                            if db_name:
                                name_map[db_name.lower()] = db_id
                except (EMSAPIError, EMSNotFoundError):
                    continue
    
            await database_cache.set(cache_key, name_map)
    
        # Case-insensitive lookup
        resolved = name_map.get(database_ref.lower())
        if resolved is not None:
            return resolved
    
        # Not found
        available = sorted(set(name_map.keys()))[:10]
        raise ValueError(
            f"Database not found: '{database_ref}'. "
            f"Available databases include: {', '.join(available)}"
            f"{'...' if len(name_map) > 10 else ''}. "
            "Use list_databases to browse available databases."
        )
    
    
    def _is_entity_type_database(database_id: str) -> bool:
        """Check if a database ID is an entity-type database.
    
        Entity-type databases (containing ``[entity-type]`` but not
        ``[entity-type-group]``) don't support the field search endpoint.
        Fields must be discovered via browse mode or deep search instead.
    
        Args:
  • Tool registration and export from tools package. get_field_info is imported from discovery module and listed in __all__ for public API exposure.
    from ems_mcp.tools.discovery import (
        find_fields,
        get_field_info,
        get_result_id,
        list_databases,
        list_ems_systems,
        search_analytics,
    )
    from ems_mcp.tools.query import (
        query_database,
        query_flight_analytics,
    )
    
    __all__ = [
        "list_ems_systems",
        "list_databases",
        "find_fields",
        "get_field_info",
        "get_result_id",
        "search_analytics",
        "query_database",
        "query_flight_analytics",
        "get_assets",
        "ping_system",
    ]
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It describes what information is returned (field details with discrete value mappings) and hints at functionality (auto-resolution of string labels in filters). However, it doesn't mention performance characteristics, error conditions, authentication requirements, or rate limits. For a tool with zero annotation coverage, this is adequate but leaves gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with clear sections: purpose statement, usage guidance, parameters explanation, and return value description. Every sentence earns its place by providing essential information. The text is front-loaded with the core purpose and most important usage guidance.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has an output schema (which handles return value documentation), the description provides good coverage of purpose, usage context, and parameter semantics. With no annotations, it could benefit from more behavioral details, but the presence of an output schema reduces the need to describe return values. The description is reasonably complete for this type of metadata retrieval tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description must compensate for the lack of parameter documentation. It provides a dedicated 'Args' section that explains all three parameters: ems_system_id, database_id, and field_id. The field_id explanation is particularly helpful with examples and multiple valid formats. This adds significant value beyond what the bare schema provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'Get' and resource 'field metadata' with specific details about what metadata is included (type, units, discrete value mappings). It distinguishes from sibling tools by focusing specifically on field metadata rather than listing fields (find_fields) or querying data (query_database). The purpose is specific and well-defined.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context for when to use this tool: 'Essential for discrete fields: shows numeric code-to-label mappings needed for filtering.' It explains that string labels in filters are auto-resolved but this tool should be used to verify available values. However, it doesn't explicitly state when NOT to use it or mention specific alternatives among the sibling tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mattsq/ems-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server