Emerging - Proven in GitHub integration, ready for broader adoption
During Phase 2 of MCP+Spatial migration (ADR-013), integration routers need to provide backward-compatible interfaces while delegating to new MCP spatial adapters. This creates a challenge:
get_recent_issues(), get_issue())list_github_issues_direct(), get_github_issue_direct())Add thin adapter methods to integration routers that:
Interface Stability via Delegation - Router provides unchanging public interface while implementation evolves to use MCP protocol and spatial intelligence.
Lazy Initialization - Router initializes async resources (like API tokens) on first use rather than during construction, using double-check locking pattern.
GitHubIntegrationRouter (stable interface)
├── __init__() - sync initialization
├── initialize() - async initialization (idempotent)
├── get_recent_issues() - adapter method → list_github_issues_direct()
├── get_issue() - adapter method → get_github_issue_direct()
├── get_open_issues() - adapter method → get_recent_issues()
└── _mcp_adapter: GitHubMCPSpatialAdapter
├── list_github_issues_direct() - MCP implementation
├── get_github_issue_direct() - MCP implementation
└── configure_github_api() - async token setup
# File: services/integrations/github/github_integration_router.py
class GitHubIntegrationRouter:
"""
Integration router for GitHub operations.
Provides stable interface while delegating to MCP+Spatial implementation.
ARCHITECTURAL PATTERN (ADR-013 Phase 2):
- Consumers call router methods (get_recent_issues, etc.)
- Router delegates to MCP+Spatial adapter
- Adapter provides 8-dimensional spatial context
- Adapter handles MCP protocol and circuit breakers
"""
def __init__(self, config_service: Optional[GitHubConfigService] = None):
"""Initialize router with sync operations only."""
self.config_service = config_service or GitHubConfigService()
self.mcp_adapter = GitHubMCPSpatialAdapter()
# Lazy initialization tracking
self._initialized = False
self._initialization_lock = None # Created in async context
async def initialize(self):
"""
Initialize the router asynchronously (idempotent).
Uses double-check locking pattern to ensure:
- Only one initialization occurs
- Thread-safe in async context
- Safe to call multiple times
"""
# Skip if already initialized
if self._initialized:
return
# Create lock if needed (first async call)
if self._initialization_lock is None:
import asyncio
self._initialization_lock = asyncio.Lock()
# Double-check pattern
async with self._initialization_lock:
if self._initialized:
return
# Configure MCP adapter with authentication
token = self.config_service.get_authentication_token()
if token:
await self.mcp_adapter.configure_github_api(token)
self._initialized = True
async def get_recent_issues(
self,
repo: str = None,
limit: int = 10,
state: str = "open"
) -> List[Dict[str, Any]]:
"""
Get recent issues (adapter method).
ADAPTER METHOD: Delegates to MCP spatial adapter.
Uses lazy initialization to ensure token loaded.
"""
# Lazy initialization
if not self._initialized:
await self.initialize()
# Delegate to MCP adapter (different method name)
return await self.mcp_adapter.list_github_issues_direct(
repository=repo,
limit=limit,
state=state
)
async def get_issue(
self,
issue_number: int,
repo: str = None
) -> Optional[Dict[str, Any]]:
"""
Get single issue by number (adapter method).
ADAPTER METHOD: Delegates to MCP spatial adapter.
Uses lazy initialization to ensure token loaded.
"""
# Lazy initialization
if not self._initialized:
await self.initialize()
# Delegate to MCP adapter (different method name + parameter format)
return await self.mcp_adapter.get_github_issue_direct(
issue_number=str(issue_number), # MCP expects string
repository=repo
)
async def get_open_issues(
self,
repo: str = None,
limit: int = 10
) -> List[Dict[str, Any]]:
"""
Get open issues (adapter method).
ADAPTER METHOD: Delegates to another adapter method.
"""
return await self.get_recent_issues(
repo=repo,
limit=limit,
state="open"
)
ADAPTER METHOD commentsinitialize() in adapter methods (causes token not loaded)initialize() idempotent (can cause duplicate setup)services/integrations/github/github_integration_router.py - Complete implementation
get_recent_issues() adapter method (lines 331-349)get_issue() adapter method (lines 183-200)get_open_issues() adapter method (lines 307-328)initialize() idempotent async init (lines 119-156)tests/features/test_morning_standup.py - Feature-layer usage
github_domain_service.get_recent_issues() which uses adaptertests/integration/test_github_mcp_router_integration.py - Integration testing
class GitHubIntegrationRouter:
def get_recent_issues(self, limit=10):
# Direct GitHub API calls
return self._github_client.list_issues(limit=limit)
class GitHubIntegrationRouter:
async def get_recent_issues(self, limit=10):
# Adapter method delegates to MCP
return await self.mcp_adapter.list_github_issues_direct(limit=limit)
# Router retired, consumers call MCP adapter directly
mcp_adapter = GitHubMCPSpatialAdapter()
issues = await mcp_adapter.list_github_issues_direct(limit=10)
dev/2025/10/19/integration-fixes-complete.mdPattern created: October 19, 2025 Sprint: A4 (CORE-STAND-FOUND) Status: Emerging (proven in production)