Integration Playbook
Complete Integration Guide: All integration patterns, setup guides, and best practices for connecting with the AltSportsLeagues.ai platform.
Integration Architecture Overview
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Integration Ecosystem β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Frontend (altsportsleagues.ai) β
β β (HTTPS/WebSocket) β
β Backend API (api.altsportsleagues.ai/v1/*) β
β β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β MCP Servers (Model Context Protocol) β β
β β ββ n8n-mcp (workflow automation) β β
β β ββ league-discovery (cross-comparison) β β
β β ββ atlassian (Jira/Confluence) β β
β β ββ google-workspace (Gmail/Calendar/Drive) β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Data Layer (Unified Access) β β
β β ββ Neo4j (graph relationships) β β
β β ββ Supabase (relational data) β β
β β ββ Firebase (auth & real-time) β β
β β ββ ChromaDB (embeddings) β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββMCP Server Integration
Understanding MCP (Model Context Protocol)
MCP provides a standardized way to connect AI systems with external tools and data sources. The platform uses MCP servers for modular, scalable integrations.
Key Concepts:
- Tools: Functions that AI can call (e.g., search leagues, create workflows)
- Resources: Data sources that AI can access (e.g., league database, documentation)
- Prompts: Pre-defined templates for common tasks
Setting Up MCP Servers
Configuration File (.cursor/mcp.json)
{
"mcpServers": {
"n8n-mcp": {
"url": "https://altsportsdata.app.n8n.cloud",
"apiKey": "${N8N_API_KEY}"
},
"league-discovery": {
"command": "python",
"args": ["-m", "apps.backend.mcp_servers.servers.league_discovery_cross_comparison_mcp"],
"env": {
"SUPABASE_URL": "${SUPABASE_URL}",
"SUPABASE_KEY": "${SUPABASE_KEY}"
}
}
}
}Environment Variables Required
# n8n Integration
N8N_API_KEY=your_n8n_api_key
N8N_BASE_URL=https://altsportsdata.app.n8n.cloud
# Database Connections
SUPABASE_URL=https://vljfrdsqtmdujhoxwtig.supabase.co
SUPABASE_KEY=your_supabase_key
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=your_password
# Firebase
FIREBASE_PROJECT_ID=your_project_id
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
# Google Workspace
GMAIL_CLIENT_ID=your_client_id
GMAIL_CLIENT_SECRET=your_client_secret
GMAIL_REFRESH_TOKEN=your_refresh_tokenCreating a Custom MCP Server
FastMCP Example (Python)
# apps/backend/mcp_servers/servers/custom_league_mcp.py
from fastmcp import FastMCP
from typing import Optional
mcp = FastMCP("custom-league-server")
@mcp.tool()
async def search_leagues(
sport: str,
region: Optional[str] = None,
limit: int = 10
) -> list[dict]:
"""
Search for sports leagues by sport type and region.
Args:
sport: Sport type (e.g., basketball, soccer)
region: Geographic region filter
limit: Maximum results to return
"""
# Implementation using data_layer
from data_layer.shared.neo4j_utils import query_neo4j
cypher = """
MATCH (l:League)-[:PLAYS]->(s:Sport {name: $sport})
WHERE $region IS NULL OR l.region = $region
RETURN l
LIMIT $limit
"""
results = await query_neo4j(cypher, {
"sport": sport,
"region": region,
"limit": limit
})
return [dict(r["l"]) for r in results]
@mcp.resource("league://schema")
def get_league_schema() -> str:
"""Get the League data schema"""
return """
League Schema:
- id: string (unique identifier)
- name: string
- sport: string
- region: string
- founded: number
- teams_count: number
"""
if __name__ == "__main__":
mcp.run()TypeScript MCP Example
// .claude/mcp/custom-server/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server({
name: "custom-league-server",
version: "1.0.0"
}, {
capabilities: {
tools: {},
resources: {}
}
});
// Define a tool
server.setRequestHandler("tools/call", async (request) => {
if (request.params.name === "search_leagues") {
const { sport, region, limit } = request.params.arguments;
// Implementation
const response = await fetch(
`https://api.altsportsleagues.ai/v1/leagues?sport=${sport}®ion=${region}&limit=${limit}`
);
return {
content: [{
type: "text",
text: JSON.stringify(await response.json(), null, 2)
}]
};
}
});
// Start server
const transport = new StdioServerTransport();
await server.connect(transport);Testing MCP Servers
# Test MCP server directly
python -m apps.backend.mcp_servers.servers.custom_league_mcp
# Test via Claude Code
# Use MCP tools in Claude Code with /mcp command
# Validate with n8n-mcp
# Use n8n_diagnostic tool to verify connectivityn8n Workflow Integration
Workflow Architecture
n8n provides the automation backbone for league onboarding and data processing.
Workflow Structure:
orchestrator/parent-agent.json # Main router
agents/email-agent.json # Email processing
agents/calendar-agent.json # Calendar management
agents/contact-agent.json # Contact management
agents/content-creator-agent.json # Content generationSetting Up n8n Integration
1. Install n8n (Self-Hosted)
# Docker Compose
docker run -d \
--name n8n \
-p 5678:5678 \
-e N8N_BASIC_AUTH_ACTIVE=true \
-e N8N_BASIC_AUTH_USER=admin \
-e N8N_BASIC_AUTH_PASSWORD=your_password \
-v ~/.n8n:/home/node/.n8n \
n8nio/n8n2. Configure OAuth for Google Workspace
In n8n UI β Settings β Credentials:
- Gmail: OAuth2 credentials
- Google Calendar: OAuth2 credentials
- Google Drive: OAuth2 credentials
Required scopes:
https://www.googleapis.com/auth/gmail.modify
https://www.googleapis.com/auth/calendar
https://www.googleapis.com/auth/drive.file3. Create a League Onboarding Workflow
Example Workflow (Simplified):
{
"name": "League Onboarding",
"nodes": [
{
"name": "Gmail Trigger",
"type": "n8n-nodes-base.gmailTrigger",
"parameters": {
"filters": {
"subject": "New League Questionnaire"
}
}
},
{
"name": "Extract Attachments",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"url": "https://api.altsportsleagues.ai/v1/email/extract-attachments",
"method": "POST",
"body": "={{$json.attachments}}"
}
},
{
"name": "Process with AI",
"type": "n8n-nodes-base.openAi",
"parameters": {
"operation": "completion",
"prompt": "Extract league information from: {{$json.text}}"
}
},
{
"name": "Store in Supabase",
"type": "n8n-nodes-base.supabase",
"parameters": {
"operation": "insert",
"table": "leagues",
"data": "={{$json}}"
}
},
{
"name": "Create Graph Node",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"url": "https://api.altsportsleagues.ai/v1/neo4j/create-league",
"method": "POST",
"body": "={{$json}}"
}
},
{
"name": "Send Confirmation",
"type": "n8n-nodes-base.gmail",
"parameters": {
"operation": "send",
"to": "={{$json.email}}",
"subject": "League Onboarding Complete",
"message": "Your league has been successfully added!"
}
}
]
}4. Upload Workflows via MCP
# Using n8n-mcp server
from fastmcp import FastMCP
mcp = FastMCP.connect("n8n-mcp")
# Create workflow
workflow = mcp.call_tool("n8n_create_workflow", {
"name": "League Onboarding",
"nodes": [...],
"connections": {...}
})
# Activate workflow
mcp.call_tool("n8n_activate_workflow", {
"id": workflow["id"]
})Webhook Integration Pattern
// Frontend trigger
const response = await fetch('/api/v1/webhooks/n8n/league-onboarding', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
leagueData: formData,
attachments: uploadedFiles
})
});
// n8n receives webhook
// β Processes data
// β Calls backend API
// β Updates databases
// β Sends notificationsBackend API Integration
Authentication
All API calls require Firebase Authentication tokens.
// Get Firebase token
import { getAuth } from 'firebase/auth';
const auth = getAuth();
const user = auth.currentUser;
const token = await user.getIdToken();
// Make API call
const response = await fetch('https://api.altsportsleagues.ai/v1/leagues', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});API Client Pattern
// lib/api-client.ts
export class AltSportsAPIClient {
private baseUrl: string;
private token: string;
constructor(token: string) {
this.baseUrl = 'https://api.altsportsleagues.ai';
this.token = token;
}
private async request(endpoint: string, options: RequestInit = {}) {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
...options,
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json',
...options.headers
}
});
if (!response.ok) {
throw new Error(`API Error: ${response.statusText}`);
}
return response.json();
}
async getLeagues(params?: {
sport?: string;
region?: string;
limit?: number;
}) {
const query = new URLSearchParams(params as any).toString();
return this.request(`/v1/leagues?${query}`);
}
async getLeague(id: string) {
return this.request(`/v1/leagues/${id}`);
}
async searchLeagues(query: string) {
return this.request(`/v1/leagues/discovery?query=${query}`);
}
}
// Usage
const client = new AltSportsAPIClient(token);
const leagues = await client.getLeagues({ sport: 'basketball' });Error Handling Pattern
try {
const leagues = await client.getLeagues();
} catch (error) {
if (error instanceof APIError) {
// Handle specific API errors
switch (error.statusCode) {
case 401:
// Redirect to login
router.push('/login');
break;
case 429:
// Rate limited
toast.error('Too many requests. Please try again later.');
break;
case 500:
// Server error
toast.error('Server error. Please try again.');
break;
}
}
}Firebase Integration
Authentication Setup
// lib/firebase.ts
import { initializeApp } from 'firebase/app';
import { getAuth, GoogleAuthProvider } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID
};
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export const db = getFirestore(app);
export const googleProvider = new GoogleAuthProvider();Authentication Flow
// components/LoginButton.tsx
import { signInWithPopup } from 'firebase/auth';
import { auth, googleProvider } from '@/lib/firebase';
export function LoginButton() {
const handleLogin = async () => {
try {
const result = await signInWithPopup(auth, googleProvider);
const token = await result.user.getIdToken();
// Store token for API calls
localStorage.setItem('authToken', token);
// Redirect to dashboard
router.push('/dashboard');
} catch (error) {
console.error('Login failed:', error);
}
};
return <button onClick={handleLogin}>Sign in with Google</button>;
}Real-time Data Sync
// hooks/useLeagueUpdates.ts
import { doc, onSnapshot } from 'firebase/firestore';
import { db } from '@/lib/firebase';
export function useLeagueUpdates(leagueId: string) {
const [league, setLeague] = useState(null);
useEffect(() => {
const unsubscribe = onSnapshot(
doc(db, 'leagues', leagueId),
(doc) => {
setLeague({ id: doc.id, ...doc.data() });
}
);
return () => unsubscribe();
}, [leagueId]);
return league;
}Database Integrations
Neo4j Graph Queries
# data_layer/shared/neo4j_utils.py
from neo4j import GraphDatabase
class Neo4jClient:
def __init__(self, uri: str, user: str, password: str):
self.driver = GraphDatabase.driver(uri, auth=(user, password))
async def query(self, cypher: str, params: dict = None):
with self.driver.session() as session:
result = session.run(cypher, params or {})
return [dict(record) for record in result]
async def create_league(self, league_data: dict):
cypher = """
CREATE (l:League {
id: $id,
name: $name,
sport: $sport,
region: $region
})
RETURN l
"""
return await self.query(cypher, league_data)
async def find_related_leagues(self, league_id: str):
cypher = """
MATCH (l:League {id: $league_id})-[:PLAYS]->(s:Sport)
MATCH (related:League)-[:PLAYS]->(s)
WHERE related.id <> $league_id
RETURN related
"""
return await self.query(cypher, {"league_id": league_id})Supabase Integration
// lib/supabase.ts
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
// Query leagues
export async function getLeagues() {
const { data, error } = await supabase
.from('leagues')
.select('*')
.order('name', { ascending: true });
if (error) throw error;
return data;
}
// Insert league
export async function createLeague(league: LeagueInsert) {
const { data, error } = await supabase
.from('leagues')
.insert(league)
.select()
.single();
if (error) throw error;
return data;
}
// Real-time subscription
export function subscribeToLeagueUpdates(
leagueId: string,
callback: (league: League) => void
) {
return supabase
.channel(`league:${leagueId}`)
.on('postgres_changes', {
event: '*',
schema: 'public',
table: 'leagues',
filter: `id=eq.${leagueId}`
}, (payload) => {
callback(payload.new as League);
})
.subscribe();
}Complete Integration Example: League Onboarding Flow
End-to-End Flow
1. User submits questionnaire (Frontend)
β
2. Frontend calls: POST /api/v1/onboarding/submit
β
3. Backend validates and triggers n8n webhook
β
4. n8n workflow processes:
- Extracts PDF attachments (if any)
- Classifies intent with AI
- Routes to appropriate agent
β
5. Email agent processes:
- Parses league information
- Validates data quality
- Calls backend API
β
6. Backend stores data:
- Supabase: Structured league data
- Neo4j: Graph relationships
- Firebase: Real-time updates
β
7. Backend generates response:
- Contract draft
- Next steps
- Calendar invite
β
8. n8n sends notifications:
- Email to league owner
- Slack message to team
- Calendar event created
β
9. Frontend updates in real-time:
- Dashboard shows new league
- Graph visualization updates
- User receives confirmationImplementation Code
// Frontend: Submit onboarding form
async function handleSubmit(formData: LeagueOnboardingForm) {
const token = await auth.currentUser.getIdToken();
const response = await fetch('/api/v1/onboarding/submit', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
const result = await response.json();
// Listen for real-time updates
const unsubscribe = subscribeToLeagueUpdates(
result.leagueId,
(updatedLeague) => {
setLeague(updatedLeague);
toast.success('League updated!');
}
);
return result;
}# Backend: Process onboarding
from fastapi import APIRouter, Depends
from services.firebase_user_service import get_current_user
router = APIRouter(prefix="/v1/onboarding")
@router.post("/submit")
async def submit_onboarding(
data: LeagueOnboardingData,
user = Depends(get_current_user)
):
# 1. Validate data
validated = validate_league_data(data)
# 2. Store in Supabase
league = await supabase.from_("leagues").insert(validated).execute()
# 3. Create graph node in Neo4j
await neo4j.create_league_node(league.data[0])
# 4. Trigger n8n workflow
await trigger_n8n_workflow("league-onboarding", {
"league_id": league.data[0]["id"],
"user_email": user.email
})
# 5. Send real-time update via Firebase
await firebase.update_document(
f"leagues/{league.data[0]['id']}",
{"status": "processing"}
)
return {
"success": True,
"leagueId": league.data[0]["id"],
"message": "Onboarding started"
}Testing Integrations
Integration Test Suite
# tests/integration/test_onboarding_flow.py
import pytest
from httpx import AsyncClient
@pytest.mark.asyncio
async def test_complete_onboarding_flow(
client: AsyncClient,
firebase_token: str,
test_league_data: dict
):
# 1. Submit onboarding
response = await client.post(
"/v1/onboarding/submit",
json=test_league_data,
headers={"Authorization": f"Bearer {firebase_token}"}
)
assert response.status_code == 200
league_id = response.json()["leagueId"]
# 2. Verify Supabase storage
supabase_data = await supabase.from_("leagues").select("*").eq("id", league_id).execute()
assert len(supabase_data.data) == 1
# 3. Verify Neo4j node
neo4j_result = await neo4j.query(
"MATCH (l:League {id: $id}) RETURN l",
{"id": league_id}
)
assert len(neo4j_result) == 1
# 4. Verify Firebase real-time update
firebase_doc = await firebase.get_document(f"leagues/{league_id}")
assert firebase_doc["status"] == "processing"
# 5. Wait for n8n workflow completion
await asyncio.sleep(5)
# 6. Verify final state
final_response = await client.get(f"/v1/leagues/{league_id}")
assert final_response.json()["status"] == "active"Troubleshooting
Common Issues
1. MCP Server Not Connecting
# Check MCP server status
python -m apps.backend.mcp_servers.servers.league_discovery_cross_comparison_mcp
# Verify environment variables
echo $SUPABASE_URL
echo $SUPABASE_KEY
# Test connection manually
curl -X POST http://localhost:8080/mcp/call-tool \
-H "Content-Type: application/json" \
-d '{"tool": "search_leagues", "args": {"sport": "basketball"}}'2. n8n Webhook Not Triggering
- Verify webhook URL is correct
- Check n8n workflow is active
- Verify OAuth credentials are valid
- Check firewall/network settings
3. Firebase Auth Token Expired
// Auto-refresh token
auth.onIdTokenChanged(async (user) => {
if (user) {
const token = await user.getIdToken(true); // Force refresh
localStorage.setItem('authToken', token);
}
});4. Database Connection Issues
# Test Neo4j connection
from neo4j import GraphDatabase
driver = GraphDatabase.driver(
os.getenv("NEO4J_URI"),
auth=(os.getenv("NEO4J_USER"), os.getenv("NEO4J_PASSWORD"))
)
with driver.session() as session:
result = session.run("RETURN 1 as test")
print(result.single()["test"]) # Should print: 1Best Practices
- Use Environment Variables: Never hardcode credentials
- Implement Retry Logic: Networks fail, handle gracefully
- Monitor Webhook Health: Set up alerts for failed webhooks
- Version Your APIs: Use
/v1/prefix for all endpoints - Document Integration Points: Keep integration docs up-to-date
- Test End-to-End: Integration tests catch real-world issues
- Use Type Safety: TypeScript/Pydantic for all integrations
- Implement Rate Limiting: Protect against abuse
- Log Everything: Comprehensive logging aids debugging
- Cache When Possible: Reduce API calls with intelligent caching
Resources
- API Documentation: https://api.altsportsleagues.ai/docs (opens in a new tab)
- MCP Specification: https://modelcontextprotocol.io (opens in a new tab)
- n8n Documentation: https://docs.n8n.io (opens in a new tab)
- Firebase Docs: https://firebase.google.com/docs (opens in a new tab)
- Neo4j Cypher Guide: https://neo4j.com/docs/cypher-manual (opens in a new tab)
- Supabase Guide: https://supabase.com/docs (opens in a new tab)
Last Updated: November 2024 Version: 2.1.0