Skip to content

MCPToolCallResult is rejected as a type expression by strict type checkers #564

Description

@ricayanzon

Summary

MCPToolCallResult in langchain_mcp_adapters/interceptors.py is defined as
two plain module-level assignments inside if LANGGRAPH_PRESENT: / else:, with
no TypeAlias marker. Strict type checkers (Pyright/Pylance in strict mode,
mypy --strict) reject any use of the name in a type expression:

Variable not allowed in type expression (reportInvalidTypeForm)

This affects anyone implementing the public ToolCallInterceptor protocol,
whose signature (handler: Callable[[MCPToolCallRequest], Awaitable[MCPToolCallResult]]) -> MCPToolCallResult)
cannot currently be annotated without # type: ignore / # pyright: ignore
suppressions on the downstream side.

Reproduction

from collections.abc import Awaitable, Callable

from langchain_mcp_adapters.interceptors import (
    MCPToolCallRequest,
    MCPToolCallResult,
)


async def my_interceptor(
    request: MCPToolCallRequest,
    handler: Callable[[MCPToolCallRequest], Awaitable[MCPToolCallResult]],
) -> MCPToolCallResult:  # <-- Pyright: reportInvalidTypeForm
    return await handler(request)

Running Pyright (default or strict) against the snippet above yields two
reportInvalidTypeForm errors on the MCPToolCallResult annotations.

Root cause

MCPToolCallResult is currently defined as:

if LANGGRAPH_PRESENT:
    from langgraph.types import Command
    MCPToolCallResult = CallToolResult | ToolMessage | Command
else:
    MCPToolCallResult = CallToolResult | ToolMessage

Because LANGGRAPH_PRESENT is a runtime bool populated from a
try/except ImportError, type checkers cannot narrow to a single branch and
see two competing implicit aliases with no TypeAlias marker — so they
refuse to treat the name as a valid type form.

Environment

  • langchain-mcp-adapters 0.3.0 (also reproduces on main)
  • Pyright / Pylance (default and strict mode)
  • Python ≥ 3.10

Proposed fix

Annotate both branches with typing.TypeAlias (available since 3.10, within
the project's declared Requires-Python). Non-breaking — preserves the
runtime symbol and behavior. PR to follow.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions