"""Tests for the proper chain building architecture - TDD red-green cycle."""
def test_mcp_chain_initial_architecture():
"""Test that mcp_chain() creates MCPChainBuilder directly (FastMCP handles protocol layer)."""
from mcp_chain import mcp_chain, MCPChainBuilder
chain = mcp_chain()
# Should be MCPChainBuilder directly
assert isinstance(chain, MCPChainBuilder)
def test_chain_building_with_transformers():
"""Test that adding transformers creates proper MiddlewareMCPServer chain."""
from mcp_chain import mcp_chain, MiddlewareMCPServer, MCPChainBuilder
def meta_transformer(next_server, metadata_dict):
original = next_server.get_metadata()
modified = original.copy()
modified["transformed"] = True
return modified
def req_transformer(next_server, request_dict):
response = next_server.handle_request(request_dict)
modified = response.copy()
modified["transformed"] = True
return modified
# Start with MCPChainBuilder
step1 = mcp_chain()
assert isinstance(step1, MCPChainBuilder)
# Add first transformer: MiddlewareMCPServer -> MCPChainBuilder
step2 = step1.then(meta_transformer, req_transformer)
assert isinstance(step2, MiddlewareMCPServer)
assert isinstance(step2._downstream, MCPChainBuilder)
# Add second transformer: MiddlewareMCPServer -> MiddlewareMCPServer -> MCPChainBuilder
step3 = step2.then(meta_transformer, req_transformer)
assert isinstance(step3, MiddlewareMCPServer)
assert isinstance(step3._downstream, MiddlewareMCPServer)
assert isinstance(step3._downstream._downstream, MCPChainBuilder)
def test_chain_building_with_external_server():
"""Test that adding ExternalMCPServer replaces MCPChainBuilder."""
from mcp_chain import mcp_chain, MiddlewareMCPServer, ExternalMCPServer
def meta_transformer(next_server, metadata_dict):
return next_server.get_metadata()
def req_transformer(next_server, request_dict):
return next_server.handle_request(request_dict)
# Build chain with transformers
chain_with_transformers = (
mcp_chain()
.then(meta_transformer, req_transformer)
.then(meta_transformer, req_transformer)
)
# Create external server (dict-based wrapper around real MCP server)
external_server = ExternalMCPServer("test_server")
# Add external server - should replace MCPChainBuilder
final_chain = chain_with_transformers.then(external_server)
# Architecture should be: MiddlewareMCPServer -> MiddlewareMCPServer -> ExternalMCPServer
assert isinstance(final_chain, MiddlewareMCPServer)
assert isinstance(final_chain._downstream, MiddlewareMCPServer)
assert isinstance(final_chain._downstream._downstream, ExternalMCPServer)
def test_end_to_end_dict_based_execution():
"""RED: Test that the entire chain works with dict-based transformers."""
from mcp_chain import mcp_chain
# Create a mock dict-based server (simulating ExternalMCPServer)
class MockDictBasedServer:
def get_metadata(self):
return {"tools": [{"name": "base_tool", "description": "base"}]}
def handle_request(self, request_dict):
return {"result": "base_response", "received": request_dict}
def first_meta_transformer(next_server, metadata_dict):
# Note: metadata_dict is ignored for metadata transformers
original = next_server.get_metadata()
modified = original.copy()
for tool in modified.get("tools", []):
tool["description"] = "first_" + tool["description"]
return modified
def first_req_transformer(next_server, request_dict):
modified_request = request_dict.copy()
modified_request["first_middleware"] = True
response = next_server.handle_request(modified_request)
modified_response = response.copy()
modified_response["first_processed"] = True
return modified_response
def second_meta_transformer(next_server, metadata_dict):
original = next_server.get_metadata()
modified = original.copy()
for tool in modified.get("tools", []):
tool["description"] = "second_" + tool["description"]
return modified
def second_req_transformer(next_server, request_dict):
modified_request = request_dict.copy()
modified_request["second_middleware"] = True
response = next_server.handle_request(modified_request)
modified_response = response.copy()
modified_response["second_processed"] = True
return modified_response
mock_server = MockDictBasedServer()
# Build the full chain
chain = (
mcp_chain()
.then(first_meta_transformer, first_req_transformer)
.then(second_meta_transformer, second_req_transformer)
.then(mock_server)
)
# Test metadata flow: transformers -> ExternalMCPServer dict (FastMCP handles JSON protocol layer)
metadata = chain.get_metadata()
assert isinstance(metadata, dict)
# Should be transformed by second_meta_transformer(first_meta_transformer(base))
assert metadata["tools"][0]["description"] == "first_second_base"
# Test request flow
request_dict = {"method": "test", "params": {"value": 123}}
response = chain.handle_request(request_dict)
assert isinstance(response, dict)
assert response["result"] == "base_response"
assert response["first_processed"] is True
assert response["second_processed"] is True
assert response["received"]["first_middleware"] is True
assert response["received"]["second_middleware"] is True
assert response["received"]["method"] == "test"
assert response["received"]["params"]["value"] == 123
def test_complete_architecture_with_multiple_middlewares():
"""Test complex middleware chain with authentication and logging layers."""
from mcp_chain import mcp_chain, MiddlewareMCPServer
# Create a mock dict-based downstream
class MockExternalServer:
def get_metadata(self):
return {
"tools": [
{"name": "calc", "description": "calculator"},
{"name": "weather", "description": "weather info"},
]
}
def handle_request(self, request_dict):
return {
"result": "external_response",
"method": request_dict.get("method"),
"params": request_dict.get("params"),
"server": "external",
}
# Create dict-based transformers
def auth_metadata_transformer(next_server, metadata_dict):
# Add authentication info to tools
result = next_server.get_metadata()
modified = result.copy()
for tool in modified.get("tools", []):
tool["auth_required"] = True
return modified
def auth_request_transformer(next_server, request_dict):
# Add auth headers and call downstream
modified_request = request_dict.copy()
modified_request["auth_token"] = "bearer_token_123"
response = next_server.handle_request(modified_request)
# Add auth info to response
modified_response = response.copy()
modified_response["authenticated"] = True
return modified_response
def logging_metadata_transformer(next_server, metadata_dict):
# Add logging info to metadata
result = next_server.get_metadata()
modified = result.copy()
modified["logged"] = True
return modified
def logging_request_transformer(next_server, request_dict):
# Log request and add logging marker
modified_request = request_dict.copy()
modified_request["request_logged"] = True
response = next_server.handle_request(modified_request)
# Log response and add marker
modified_response = response.copy()
modified_response["response_logged"] = True
return modified_response
mock_external = MockExternalServer()
# Build the complete chain: auth -> logging -> external
complete_chain = (
mcp_chain()
.then(auth_metadata_transformer, auth_request_transformer)
.then(logging_metadata_transformer, logging_request_transformer)
.then(mock_external)
)
# Verify architecture structure
assert isinstance(complete_chain, MiddlewareMCPServer) # Auth middleware
assert isinstance(
complete_chain._downstream, MiddlewareMCPServer
) # Logging middleware
assert isinstance(
complete_chain._downstream._downstream, MockExternalServer
) # External server
# Test end-to-end metadata flow
metadata = complete_chain.get_metadata()
assert isinstance(metadata, dict)
assert metadata["logged"] is True
assert metadata["tools"][0]["auth_required"] is True
assert metadata["tools"][0]["name"] == "calc" # From external server
# Test end-to-end request flow
request_dict = {"method": "calc", "params": {"operation": "add", "a": 1, "b": 2}}
response = complete_chain.handle_request(request_dict)
assert isinstance(response, dict)
# Verify the response has markers from all layers
assert response["result"] == "external_response" # From external server
assert response["server"] == "external" # From external server
assert response["authenticated"] is True
assert response["response_logged"] is True
# Verify the request was properly transformed through all layers
assert response["method"] == "calc" # Original method preserved
assert response["params"]["operation"] == "add" # Original params preserved