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.
Summary
MCPToolCallResultinlangchain_mcp_adapters/interceptors.pyis defined astwo plain module-level assignments inside
if LANGGRAPH_PRESENT: / else:, withno
TypeAliasmarker. Strict type checkers (Pyright/Pylance in strict mode,mypy --strict) reject any use of the name in a type expression:This affects anyone implementing the public
ToolCallInterceptorprotocol,whose signature (
handler: Callable[[MCPToolCallRequest], Awaitable[MCPToolCallResult]]) -> MCPToolCallResult)cannot currently be annotated without
# type: ignore/# pyright: ignoresuppressions on the downstream side.
Reproduction
Running Pyright (default or strict) against the snippet above yields two
reportInvalidTypeFormerrors on theMCPToolCallResultannotations.Root cause
MCPToolCallResultis currently defined as:Because
LANGGRAPH_PRESENTis a runtimeboolpopulated from atry/except ImportError, type checkers cannot narrow to a single branch andsee two competing implicit aliases with no
TypeAliasmarker — so theyrefuse to treat the name as a valid type form.
Environment
langchain-mcp-adapters0.3.0 (also reproduces onmain)Proposed fix
Annotate both branches with
typing.TypeAlias(available since 3.10, withinthe project's declared
Requires-Python). Non-breaking — preserves theruntime symbol and behavior. PR to follow.