"""Complex chain building logic tests."""
import pytest
def test_mcp_chain_builder_has_then_method():
"""Test that MCPChainBuilder has a then method."""
from mcp_chain import mcp_chain
builder = mcp_chain()
# Should have a then method
assert hasattr(builder, "then")
assert callable(builder.then)
def test_mcp_chain_builder_then_returns_downstream_server():
"""Test that MCPChainBuilder.then() returns downstream server directly."""
from mcp_chain import mcp_chain
# Create a mock downstream server (no 'then' method)
class MockServer:
def get_metadata(self):
return {"tools": [{"name": "test_tool"}]}
def handle_request(self, request):
return {"result": "success"}
builder = mcp_chain()
downstream = MockServer()
# then() should return the downstream server directly (not wrapped)
result = builder.then(downstream)
assert result is downstream
# Should work as MCP server
metadata = result.get_metadata()
expected = {"tools": [{"name": "test_tool"}]}
assert metadata == expected
def test_mcp_chain_builder_then_creates_middleware_with_transformers():
"""Test that MCPChainBuilder.then() creates MiddlewareMCPServer with transformers."""
from mcp_chain import mcp_chain, MiddlewareMCPServer
def metadata_transformer(next_mcp, metadata_dict):
original = next_mcp.get_metadata()
modified = original.copy()
modified["modified"] = True
return modified
def request_transformer(next_mcp, request_dict):
response = next_mcp.handle_request(request_dict)
modified = response.copy()
modified["transformed"] = True
return modified
builder = mcp_chain()
# then() with transformers should create MiddlewareMCPServer
middleware = builder.then(metadata_transformer, request_transformer)
assert isinstance(middleware, MiddlewareMCPServer)
def test_middleware_then_delegates_to_child_and_wraps():
"""Test that MiddlewareMCPServer.then() delegates to child and wraps result."""
from mcp_chain import mcp_chain, MiddlewareMCPServer
# Create a mock downstream server
class MockServer:
def get_metadata(self):
return {"tools": [{"name": "test_tool"}]}
def handle_request(self, request):
return {"result": "success"}
def then(self, *args):
"""Mock then method that returns a modified version"""
if len(args) == 1 and hasattr(args[0], "get_metadata"):
# Return a new mock that adds "delegated" flag
class DelegatedMock:
def get_metadata(self):
return {"tools": [{"name": "delegated_tool"}]}
def handle_request(self, request):
return {"result": "delegated"}
return DelegatedMock()
return self
# Create initial chain
mcp_chain()
mock_with_then = MockServer()
# Create a middleware with the mock that has 'then'
middleware = MiddlewareMCPServer(downstream_server=mock_with_then)
# Create another mock downstream
class AnotherMock:
def get_metadata(self):
return {"tools": [{"name": "another_tool"}]}
def handle_request(self, request):
return {"result": "another"}
another_mock = AnotherMock()
# MiddlewareMCPServer.then() should delegate to child.then() and wrap result
result = middleware.then(another_mock)
assert isinstance(result, MiddlewareMCPServer)
# The result should have the delegated downstream
metadata = result.get_metadata()
assert metadata["tools"][0]["name"] == "delegated_tool"
def test_mcp_chain_builder_replaces_itself_in_chain():
"""Test that MCPChainBuilder replaces itself when downstream is added."""
from mcp_chain import mcp_chain
# Create downstream server
class MockServer:
def get_metadata(self):
return {"tools": [{"name": "test_tool", "description": "test_tool"}]}
def handle_request(self, request):
return {"result": "success"}
def first_transformer(next_mcp, metadata_dict):
original = next_mcp.get_metadata()
modified = original.copy()
for tool in modified.get("tools", []):
tool["description"] = "first: " + tool.get("description", "")
return modified
def second_transformer(next_mcp, metadata_dict):
original = next_mcp.get_metadata()
modified = original.copy()
for tool in modified.get("tools", []):
tool["description"] = "second: " + tool.get("description", "")
return modified
downstream = MockServer()
# Build chain step by step
step1 = mcp_chain().then(
first_transformer, lambda next_mcp, req: next_mcp.handle_request(req)
)
# step1 is MiddlewareMCPServer(first_transformer) -> MCPChainBuilder
step2 = step1.then(
second_transformer, lambda next_mcp, req: next_mcp.handle_request(req)
)
# step2 should be MiddlewareMCPServer(first_transformer) -> MiddlewareMCPServer(second_transformer) -> MCPChainBuilder
final = step2.then(downstream)
# final should be MiddlewareMCPServer(first_transformer) -> MiddlewareMCPServer(second_transformer) -> downstream
# MCPChainBuilder should be completely replaced
# Test the execution order: first -> second -> downstream
metadata = final.get_metadata()
description = metadata["tools"][0]["description"]
assert (
description == "first: second: test_tool"
) # first wraps (second wraps (downstream))
def test_middleware_then_should_error_when_child_has_no_then():
"""Test that MiddlewareMCPServer.then() errors when child has no then method."""
from mcp_chain import MiddlewareMCPServer
# Create downstream server without then method
class MockServerNoThen:
def get_metadata(self):
return {"tools": [{"name": "test_tool"}]}
def handle_request(self, request):
return {"result": "success"}
downstream = MockServerNoThen()
middleware = MiddlewareMCPServer(downstream_server=downstream)
def transformer(next_mcp, metadata_dict):
return next_mcp.get_metadata()
# Should raise error when trying to chain on server without `then` method
with pytest.raises(ValueError, match="Cannot chain.*no `then` method"):
middleware.then(transformer, lambda next_mcp, req: next_mcp.handle_request(req))