Skip to content

Commit 805b4b1

Browse files
fix(adapters): wrap redacted MCP output in CallToolResult (#120)
* 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 * 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. --------- Co-authored-by: Saurabh Jain <saurabhjain1592@gmail.com>
1 parent 697b6d9 commit 805b4b1

5 files changed

Lines changed: 34 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ All notable changes to the AxonFlow Python SDK will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [5.1.0] - 2026-03-19
9+
10+
### Added
11+
12+
- 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.
13+
14+
### Fixed
15+
16+
- `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`.
17+
18+
---
19+
820
## [5.0.0] - 2026-03-16
921

1022
### Breaking Changes

axonflow/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Single source of truth for the AxonFlow SDK version."""
22

3-
__version__ = "5.0.0"
3+
__version__ = "5.1.0"

axonflow/adapters/langgraph.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,15 @@ def mcp_tool_interceptor(
538538
An async callable ``(request, handler) -> result`` suitable for
539539
``MultiServerMCPClient(tool_interceptors=[...])``.
540540
"""
541+
try:
542+
from mcp.types import CallToolResult, TextContent # noqa: PLC0415
543+
except ImportError as exc:
544+
msg = (
545+
"The 'mcp' package is required to use mcp_tool_interceptor. "
546+
"Install it with: pip install 'axonflow[langgraph]'"
547+
)
548+
raise ImportError(msg) from exc
549+
541550
opts = options or MCPInterceptorOptions()
542551

543552
def _default_connector_type(request: Any) -> str:
@@ -574,7 +583,9 @@ async def _interceptor(request: Any, handler: Callable[..., Any]) -> Any:
574583
output_check.block_reason or "Tool result blocked by policy"
575584
)
576585
if output_check.redacted_data is not None:
577-
return output_check.redacted_data
586+
return CallToolResult(
587+
content=[TextContent(type="text", text=output_check.redacted_data)]
588+
)
578589

579590
return result
580591

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "axonflow"
7-
version = "5.0.0"
7+
version = "5.1.0"
88
description = "AxonFlow Python SDK - Enterprise AI Governance in 3 Lines of Code"
99
readme = "README.md"
1010
license = {text = "MIT"}
@@ -52,6 +52,7 @@ dev = [
5252
"black>=23.0.0",
5353
"isort>=5.12.0",
5454
"pre-commit>=3.0.0",
55+
"mcp>=1.0.0",
5556
]
5657
docs = [
5758
"sphinx>=7.0.0",
@@ -61,9 +62,11 @@ docs = [
6162
]
6263
openai = ["openai>=1.0.0"]
6364
anthropic = ["anthropic>=0.18.0"]
65+
langgraph = ["mcp>=1.0.0"]
6466
all = [
6567
"openai>=1.0.0",
6668
"anthropic>=0.18.0",
69+
"mcp>=1.0.0",
6770
]
6871

6972
[project.urls]

tests/test_langgraph_adapter.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from unittest.mock import AsyncMock, MagicMock
88

99
import pytest
10+
from mcp.types import CallToolResult, TextContent
1011

1112
from axonflow import AxonFlow
1213
from axonflow.adapters.langgraph import AxonFlowLangGraphAdapter, MCPInterceptorOptions
@@ -192,7 +193,10 @@ async def test_returns_redacted_data_when_present(
192193

193194
result = await adapter.mcp_tool_interceptor()(MagicMock(), handler)
194195

195-
assert result == "[REDACTED]"
196+
assert isinstance(result, CallToolResult)
197+
assert len(result.content) == 1
198+
assert isinstance(result.content[0], TextContent)
199+
assert result.content[0].text == "[REDACTED]"
196200

197201
@pytest.mark.asyncio
198202
async def test_returns_original_result_when_no_redaction(

0 commit comments

Comments
 (0)