Skip to main content
Glama
josefdc

UniProt MCP Server

by josefdc

map_ids

Convert protein identifiers between different biological databases using UniProt's mapping service to access over 200 supported namespaces.

Instructions

Map identifiers between UniProt-supported namespaces.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
from_dbYes
to_dbYes
idsYes

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
to_dbYesTarget identifier namespace.
from_dbYesSource identifier namespace.
resultsNoMapping from input IDs to resolved identifiers (empty list for no match).

Implementation Reference

  • The main handler function for the 'map_ids' tool. It filters input IDs, submits a mapping job to UniProt via the client, polls for completion using _poll_mapping_job, and parses the result using parse_mapping_result. The @mcp.tool() decorator registers it as an MCP tool.
    @mcp.tool()  # type: ignore[misc]
    async def map_ids(
        from_db: str,
        to_db: str,
        ids: list[str],
        ctx: Context[ServerSession, None] | None = None,
    ) -> MappingResult:
        """Map identifiers between UniProt-supported namespaces."""
    
        filtered_ids = [identifier for identifier in ids if identifier]
        if not filtered_ids:
            return MappingResult(from_db=from_db, to_db=to_db, results={})
    
        async with new_client() as client:
            job_id = await start_id_mapping(client, from_db=from_db, to_db=to_db, ids=filtered_ids)
    
        if ctx is not None:
            await ctx.info(
                f"Submitted UniProt ID mapping job ({from_db}->{to_db}) for {len(filtered_ids)} IDs."
            )
    
        payload = await _poll_mapping_job(job_id, ctx=ctx)
        if ctx is not None:
            await ctx.info(
                f"Completed UniProt ID mapping job ({from_db}->{to_db}) for {len(filtered_ids)} IDs."
            )
        return parse_mapping_result(payload, from_db=from_db, to_db=to_db)
  • Pydantic BaseModel defining the structured output schema for the map_ids tool, including from_db, to_db, and a dictionary of results mapping input IDs to lists of target IDs.
    class MappingResult(BaseModel):
        """Outcome of UniProt ID mapping operations."""
    
        from_db: str = Field(description="Source identifier namespace.")
        to_db: str = Field(description="Target identifier namespace.")
        results: dict[str, list[str]] = Field(
            default_factory=dict,
            description="Mapping from input IDs to resolved identifiers (empty list for no match).",
        )
  • Helper function to parse the raw JSON response from UniProt ID mapping into the MappingResult model, handling various response formats, failed IDs, and deduplication.
    def parse_mapping_result(
        js: dict[str, Any],
        *,
        from_db: str,
        to_db: str,
    ) -> MappingResult:
        """Convert an ID mapping response into MappingResult."""
    
        mappings: dict[str, list[str]] = {}
    
        def register_result(source: str | None, targets: Iterable[Any]) -> None:
            if not source:
                return
            values: list[str] = []
            for target in targets:
                if isinstance(target, dict):
                    candidate = target.get("id") or target.get("identifier") or target.get("value")
                    if candidate:
                        values.append(str(candidate))
                elif target is not None:
                    values.append(str(target))
            if source not in mappings:
                mappings[source] = []
            mappings[source].extend(values)
    
        for item in js.get("results") or []:
            if not isinstance(item, dict):
                continue
            source = item.get("from") or item.get("fromId")
            to_value = item.get("to") or item.get("toId") or item.get("mappedTo")
            if isinstance(to_value, list):
                register_result(source, to_value)
            elif to_value is not None:
                register_result(source, [to_value])
            else:
                register_result(source, [])
    
        # Some responses return an explicit mapping dictionary
        for source, value in (js.get("mappedResults") or {}).items():
            if isinstance(value, list):
                register_result(source, value)
            else:
                register_result(source, [value])
    
        # Ensure failed IDs are tracked with empty lists
        for failed in js.get("failedIds") or []:
            if failed not in mappings:
                mappings[failed] = []
    
        # Normalise ordering and remove duplicates per ID
        for key, values in mappings.items():
            deduped = list(dict.fromkeys(values))
            mappings[key] = deduped
    
        return MappingResult(from_db=from_db, to_db=to_db, results=mappings)
  • Helper function to poll the UniProt ID mapping job status until completion or timeout, with progress reporting via MCP context.
    async def _poll_mapping_job(
        job_id: str,
        *,
        ctx: Context[ServerSession, None] | None = None,
    ) -> dict[str, Any]:
        """Poll the UniProt mapping job until completion or timeout."""
    
        elapsed = 0.0
        async with new_client() as client:
            while elapsed < MAPPING_MAX_WAIT:
                status = await get_mapping_status(client, job_id)
                if _mapping_is_complete(status):
                    results = await get_mapping_results(client, job_id)
                    return cast(dict[str, Any], results)
                elapsed += MAPPING_POLL_INTERVAL
                if ctx is not None:
                    progress = min(1.0, elapsed / MAPPING_MAX_WAIT)
                    await ctx.report_progress(
                        progress=progress,
                        total=1.0,
                        message="Polling UniProt ID mapping job",
                    )
                await asyncio.sleep(MAPPING_POLL_INTERVAL)
            raise UniProtClientError("ID mapping timed out waiting for completion.")

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changedv1.0.0
    • addedInput schema / title
      Added value: +"map_idsArguments"
  2. First observed

TDQS

C2.7/5.0
Behavior2/5

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

No annotations are provided, so the description must carry the full burden. It only mentions 'map identifiers' without disclosing behavioral traits such as id limits, mapping directionality, or side effects.

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

Conciseness4/5

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

Single sentence, no wasted words. However, a slight expansion to clarify parameter roles would improve utility without harming conciseness.

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

Completeness2/5

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

Despite having an output schema, the description fails to cover parameter semantics and usage context. For a tool with three undocumented parameters, this is insufficient.

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

Parameters2/5

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

Input schema has 0% description coverage. The description implies that 'from_db' and 'to_db' are namespaces and 'ids' are identifiers, but it does not explain valid values or formats, leaving ambiguity.

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

Purpose4/5

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

The description states it maps identifiers between UniProt-supported namespaces, clearly indicating the verb and resource. It distinguishes from sibling tools that fetch entries or sequences. However, it lacks specificity about the mapping operation itself.

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

Usage Guidelines2/5

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

No guidance on when to use this tool versus alternatives. The description does not provide context on prerequisites, limitations, or scenarios for exclusion.

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