From 2b0e9675df2f6fb96808dfc9688716ac6069d86c Mon Sep 17 00:00:00 2001 From: Gregory Zak Date: Mon, 16 Mar 2026 13:23:56 -0700 Subject: [PATCH 1/2] fix(adapters): wrap redacted MCP output in CallToolResult When mcp_check_output returns redacted_data, the interceptor was returning a plain str. langchain-mcp-adapters expects MCPToolCallResult (CallToolResult | ToolMessage | Command) and calls .content on the return value, causing AttributeError at runtime. Now wraps the redacted string in a CallToolResult with a single TextContent block, which is the correct return type for the interceptor protocol. Also adds mcp>=1.0.0 as a new 'langgraph' optional extra and to dev dependencies, with a lazy import that surfaces a clear error message if mcp is not installed. Fixes #119 --- CHANGELOG.md | 8 ++++++++ axonflow/_version.py | 2 +- axonflow/adapters/langgraph.py | 13 ++++++++++++- pyproject.toml | 6 +++++- tests/test_langgraph_adapter.py | 6 +++++- 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a113d41..c95d365 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to the AxonFlow Python SDK will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [5.1.0] - 2026-03-19 + +### Fixed + +- `mcp_tool_interceptor()` now wraps redacted output in a `CallToolResult` instead of returning a plain `str`. Previously, when `mcp_check_output` applied redaction, the interceptor returned the redacted string directly, causing `AttributeError: 'str' object has no attribute 'content'` in `langchain-mcp-adapters`. Adds `mcp>=1.0.0` as a new `langgraph` optional extra (`pip install 'axonflow[langgraph]'`). + +--- + ## [5.0.0] - 2026-03-16 ### Breaking Changes diff --git a/axonflow/_version.py b/axonflow/_version.py index e8038eb..91284fe 100644 --- a/axonflow/_version.py +++ b/axonflow/_version.py @@ -1,3 +1,3 @@ """Single source of truth for the AxonFlow SDK version.""" -__version__ = "5.0.0" +__version__ = "5.1.0" diff --git a/axonflow/adapters/langgraph.py b/axonflow/adapters/langgraph.py index 5d25822..06456bd 100644 --- a/axonflow/adapters/langgraph.py +++ b/axonflow/adapters/langgraph.py @@ -538,6 +538,15 @@ def mcp_tool_interceptor( An async callable ``(request, handler) -> result`` suitable for ``MultiServerMCPClient(tool_interceptors=[...])``. """ + try: + from mcp.types import CallToolResult, TextContent # noqa: PLC0415 + except ImportError as exc: + msg = ( + "The 'mcp' package is required to use mcp_tool_interceptor. " + "Install it with: pip install 'axonflow[langgraph]'" + ) + raise ImportError(msg) from exc + opts = options or MCPInterceptorOptions() def _default_connector_type(request: Any) -> str: @@ -574,7 +583,9 @@ async def _interceptor(request: Any, handler: Callable[..., Any]) -> Any: output_check.block_reason or "Tool result blocked by policy" ) if output_check.redacted_data is not None: - return output_check.redacted_data + return CallToolResult( + content=[TextContent(type="text", text=output_check.redacted_data)] + ) return result diff --git a/pyproject.toml b/pyproject.toml index b1a7969..abc6215 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "axonflow" -version = "5.0.0" +version = "5.1.0" description = "AxonFlow Python SDK - Enterprise AI Governance in 3 Lines of Code" readme = "README.md" license = {text = "MIT"} @@ -52,6 +52,7 @@ dev = [ "black>=23.0.0", "isort>=5.12.0", "pre-commit>=3.0.0", + "mcp>=1.0.0", ] docs = [ "sphinx>=7.0.0", @@ -61,9 +62,11 @@ docs = [ ] openai = ["openai>=1.0.0"] anthropic = ["anthropic>=0.18.0"] +langgraph = ["mcp>=1.0.0"] all = [ "openai>=1.0.0", "anthropic>=0.18.0", + "mcp>=1.0.0", ] [project.urls] @@ -167,6 +170,7 @@ show_column_numbers = true module = "tests.*" disallow_untyped_defs = false + [tool.pytest.ini_options] asyncio_mode = "auto" testpaths = ["tests"] diff --git a/tests/test_langgraph_adapter.py b/tests/test_langgraph_adapter.py index efd0b59..69e54bf 100644 --- a/tests/test_langgraph_adapter.py +++ b/tests/test_langgraph_adapter.py @@ -7,6 +7,7 @@ from unittest.mock import AsyncMock, MagicMock import pytest +from mcp.types import CallToolResult, TextContent from axonflow import AxonFlow from axonflow.adapters.langgraph import AxonFlowLangGraphAdapter, MCPInterceptorOptions @@ -192,7 +193,10 @@ async def test_returns_redacted_data_when_present( result = await adapter.mcp_tool_interceptor()(MagicMock(), handler) - assert result == "[REDACTED]" + assert isinstance(result, CallToolResult) + assert len(result.content) == 1 + assert isinstance(result.content[0], TextContent) + assert result.content[0].text == "[REDACTED]" @pytest.mark.asyncio async def test_returns_original_result_when_no_redaction( From e59a33182d4c277e31c70ca7df2feae0a26eb0fb Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Fri, 20 Mar 2026 12:36:40 +0100 Subject: [PATCH 2/2] fix: split changelog into Added + Fixed sections, remove stray blank line Separate the langgraph optional extra (Added) from the CallToolResult bugfix (Fixed) to clarify why this is a minor version bump. --- CHANGELOG.md | 6 +++++- pyproject.toml | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c95d365..8af5986 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [5.1.0] - 2026-03-19 +### Added + +- New `langgraph` optional extra for MCP tool interception: `pip install 'axonflow[langgraph]'`. The `mcp` package is now an opt-in dependency rather than being imported unconditionally at the package level. + ### Fixed -- `mcp_tool_interceptor()` now wraps redacted output in a `CallToolResult` instead of returning a plain `str`. Previously, when `mcp_check_output` applied redaction, the interceptor returned the redacted string directly, causing `AttributeError: 'str' object has no attribute 'content'` in `langchain-mcp-adapters`. Adds `mcp>=1.0.0` as a new `langgraph` optional extra (`pip install 'axonflow[langgraph]'`). +- `mcp_tool_interceptor()` now wraps redacted output in a `CallToolResult` instead of returning a plain `str`. Previously, when `mcp_check_output` applied redaction, the interceptor returned the redacted string directly, causing `AttributeError: 'str' object has no attribute 'content'` in `langchain-mcp-adapters`. --- diff --git a/pyproject.toml b/pyproject.toml index abc6215..943297f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -170,7 +170,6 @@ show_column_numbers = true module = "tests.*" disallow_untyped_defs = false - [tool.pytest.ini_options] asyncio_mode = "auto" testpaths = ["tests"]