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.")
Behavior2/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 states the action but lacks details on permissions, rate limits, error handling, or what the mapping entails (e.g., one-to-one, many-to-many). This is a significant gap for a tool with parameters and an output schema.

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 a single, efficient sentence with zero waste. It's appropriately sized and front-loaded, clearly stating the core function without unnecessary elaboration.

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

Completeness3/5

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

Given 3 parameters with 0% schema coverage and an output schema, the description is incomplete. It covers the basic purpose but lacks parameter details and behavioral context. The output schema mitigates some gaps, but overall it's minimally adequate with clear room for improvement.

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?

Schema description coverage is 0%, so the description must compensate. It implies parameters for source/target databases and IDs but doesn't explain what 'UniProt-supported namespaces' are, valid values for from_db/to_db, or ID formats. This leaves key semantics undocumented.

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 clearly states the tool's purpose: mapping identifiers between UniProt-supported namespaces. It specifies the verb 'map' and the resource 'identifiers', but doesn't differentiate from sibling tools like fetch_entry or search_uniprot, which have different functions (fetching entries vs. searching).

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?

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention prerequisites, context, or exclusions, leaving the agent to infer usage based on the purpose alone.

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/josefdc/Uniprot-MCP'

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