Skip to content

Conversation

@awg66
Copy link
Contributor

@awg66 awg66 commented Nov 10, 2025

Summary
This PR adds the @typing.runtime_checkable decorator to the following protocols:

  • LoggingMessageCallback
  • ProgressCallback
  • ToolCallInterceptor

Motivation
When these protocols are used as type hints within Pydantic V2 models (specifically when arbitrary_types_allowed=True is set), Pydantic attempts to automatically generate an isinstance validator for the field.

Because these protocols were not marked as runtime checkable, standard Python isinstance() checks fail, causing Pydantic to raise a SchemaError during model creation.

Adding @runtime_checkable allows isinstance() to work correctly with these protocols, enabling downstream users to include standard LangChain MCP callbacks and interceptors directly in their Pydantic models without needing to type them as Any.

Error Reference
Without this change, attempting to use these types in a Pydantic V2 model results in tracebacks similar to:

E   pydantic_core._pydantic_core.SchemaError: Error building "is-instance" validator:
E     SchemaError: 'cls' must be valid as the first argument to 'isinstance'

Test Plan
[x] Verified that Pydantic V2 models can now successfully use these types as fields without raising SchemaError.

(langchain-mcp-adapters) ➜  langchain-mcp-adapters git:(fix_protocol_schemas_pydantic) ✗ uv run ipython
Python 3.12.10 (main, May 17 2025, 13:40:56) [Clang 20.1.4 ]
Type 'copyright', 'credits' or 'license' for more information
IPython 9.7.0 -- An enhanced Interactive Python. Type '?' for help.
Tip: Use the IPython.lib.demo.Demo class to load any Python script as an interactive demo.

In [1]: from pydantic import BaseModel
   ...: from langchain_mcp_adapters.callbacks import LoggingMessageCallback, ProgressCallback
   ...: from langchain_mcp_adapters.interceptors import ToolCallInterceptor
   ...:
   ...: print("Attempting to build Pydantic model with Protocols...")
   ...:
   ...: try:
   ...:     # Define a model that uses the problematic types
   ...:     class TestModel(BaseModel, arbitrary_types_allowed=True):
   ...:         logging_cb: LoggingMessageCallback | None = None
   ...:         progress_cb: ProgressCallback | None = None
   ...:         interceptor: ToolCallInterceptor | None = None
   ...:
   ...:     # Instantiate it to trigger standard schema validation
   ...:     model = TestModel()
   ...:     print("\n✅ SUCCESS: Pydantic model built successfully! The fix is working.")
   ...:
   ...:     # Double check runtime_checkable is actually working
   ...:     print("Testing isinstance checks (should be True now)...")
   ...:     print(f"LoggingMessageCallback is runtime checkable: {isinstance(LoggingMessageCallback, type)}")
   ...:
   ...: except Exception as e:
   ...:     print(f"\n❌ FAILED: Still getting error:\n{e}")
   ...:

Attempting to build Pydantic model with Protocols...

✅ SUCCESS: Pydantic model built successfully! The fix is working.
Testing isinstance checks (should be True now)...
LoggingMessageCallback is runtime checkable: True

@sydney-runkle sydney-runkle merged commit 4a22391 into langchain-ai:main Nov 11, 2025
6 checks passed
@awg66 awg66 deleted the fix_protocol_schemas_pydantic branch November 12, 2025 00:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants