Skip to content

LBS Ballr MCP Server Setup Guide

Overview

The LBS Ballr MCP Server provides secure, controlled access to sports data via the Model Context Protocol (MCP). It integrates with Clerk OAuth for authentication and runs alongside your existing Aspire development environment.

Design Decisions

  • Specific tools, not generic queries — only whitelisted tools are exposed; complete control over data access, type-safe.
  • Direct Marten integration, not HTTP API — better performance, reuses domain contracts and existing infrastructure.
  • Optional authentication with graceful degradation — anonymous users get basic data; authenticated users get richer data via the existing UserContext infrastructure.
  • Clerk JWT validation — reuses existing auth patterns in the platform.

Architecture

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   LLM Client    │───▶│   MCP Server     │───▶│  Domain Layer   │
│  (Claude, etc.) │    │  (STDIO/JSON)    │    │   (Marten DB)   │
└─────────────────┘    └──────────────────┘    └─────────────────┘
                       ┌──────────────────┐
                       │ Clerk OAuth JWT  │
                       │   Validation     │
                       └──────────────────┘

Key Components

  • MCP Server: src/Apps/LBS.Ballr.McpServer/
  • Authentication: Clerk OAuth JWT validation
  • Data Access: Direct Marten queries to domain contracts
  • Tools: Whitelisted, specific query tools only

Project Structure

src/Apps/LBS.Ballr.McpServer/
├── Program.cs                          # Aspire integration & DI setup
├── appsettings.json                    # Local configuration
├── Models/
│   └── NrlPlayerInfo.cs               # Response models for MCP tools
├── Services/
│   ├── AuthenticationService.cs       # MCP authentication orchestration
│   └── ClerkJwtService.cs             # Clerk JWT validation
└── Tools/
    ├── AuthenticationTools.cs         # Auth-related MCP tools
    ├── NrlPlayerTools.cs              # NRL player search tools
    ├── CompetitionTools.cs            # Competition tools (stubbed)
    └── FixtureTools.cs                # Fixture tools (stubbed)

Setup Instructions

1. Create Clerk OAuth Application

  1. Login to Clerk Dashboard: https://dashboard.clerk.com
  2. Navigate to your application (precious-ghoul-33)
  3. Go to: Configure → Applications → OAuth Applications
  4. Click "Add OAuth Application"
  5. Configure the OAuth app:
    Name: LBS Ballr MCP Server
    Description: MCP server for sports data access
    Callback URL: http://localhost:7052/oauth/callback
    Scopes: openid, profile, email
    Grant Types: authorization_code, refresh_token
    
  6. Save and note down:
  7. Client ID (public)
  8. Client Secret (private - keep secure)

2. Update Configuration

Edit /src/Aspire/LBS.AspireHost/appsettings.json:

{
  "ClerkOAuth": {
    "ClientId": "YOUR_ACTUAL_CLIENT_ID_HERE",
    "ClientSecret": "YOUR_ACTUAL_CLIENT_SECRET_HERE", 
    "Domain": "https://precious-ghoul-33.clerk.accounts.dev"
  }
}

Replace the placeholder values with your actual Clerk OAuth credentials.

3. Run with Aspire

# From repository root
dotnet run --project src/Aspire/LBS.AspireHost/LBS.AspireHost.csproj

This will start: - Main API (LBS.Api) - MCP Server (LBS.Ballr.McpServer) - PostgreSQL database - Azure Storage emulator

The MCP server runs as a console application that communicates via STDIO using the MCP protocol.

Available MCP Tools

Authentication Tools

Authenticate

Validates a Clerk Bearer token and sets user context.

Parameters: - bearerToken (string): Clerk JWT token

Returns:

{
  "success": true,
  "message": "Authentication successful",
  "user": {
    "id": "user_123",
    "name": "John Doe",
    "email": "john@example.com",
    "roles": ["Member"],
    "isAuthenticated": true
  }
}

GetCurrentUser

Returns information about the currently authenticated user.

Returns:

{
  "id": "user_123",
  "name": "John Doe", 
  "email": "john@example.com",
  "roles": ["Member"],
  "isAuthenticated": true
}

Logout

Clears the current authentication session.

Returns:

{
  "success": true,
  "message": "Successfully logged out"
}

Data Query Tools

SearchNrlPlayers

Search NRL SuperCoach players with comprehensive filtering options.

Parameters: - bearerToken (string, optional): Authentication token for enhanced data - searchTerm (string, optional): Player name search - teams (string[], optional): Team names like "Panthers", "Storm" - positions (string[], optional): Positions like "Halfback", "Fullback" - minPoints (int, optional): Minimum total points - maxPoints (int, optional): Maximum total points - minPrice (decimal, optional): Minimum player price - maxPrice (decimal, optional): Maximum player price - minOwnership (decimal, optional): Minimum ownership percentage - maxOwnership (decimal, optional): Maximum ownership percentage - orderBy (string): Sort order like "totalPoints DESC" - skip (int): Pagination offset - take (int): Number of results to return

Returns:

{
  "players": [
    {
      "displayName": "Nathan Cleary",
      "teamName": "Panthers",
      "position": "Halfback",
      "currentPrice": 750000,
      "totalPoints": 1250,
      "averagePoints": 65.5,
      "lastRoundPoints": 78,
      "ownershipPercentage": 45.2,
      "projectedPoints": 72.5,
      "projectedMinutes": 80.0
    }
  ],
  "totalCount": 500,
  "skip": 0,
  "take": 20,
  "hasMore": true
}

GetCompetitionTeams (Stubbed)

Returns team information for a competition.

Parameters: - competitionCode (string): Competition like "NRL", "AFL" - season (int, optional): Season year

Returns: Stubbed team data for development.

GetSportingEvents (Stubbed)

Returns upcoming sporting events.

Parameters: - competitionCode (string, optional): Competition filter - teams (string[], optional): Team name filters - daysAhead (int): Number of days to look ahead

Returns: Stubbed sporting event data for development.

Authentication Flow

1. For Unauthenticated Users

Tools work without authentication but return basic data only:

// Basic player search - limited data
SearchNrlPlayers({
  searchTerm: "Smith",
  teams: ["Panthers"],
  take: 10
})

2. For Authenticated Users

  1. Get Bearer Token: User authenticates via your web application using Clerk
  2. Authenticate with MCP: Pass the Bearer token to the MCP server
  3. Enhanced Data Access: Subsequent tool calls return richer data
// Step 1: Authenticate
Authenticate({
  bearerToken: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
})

// Step 2: Enhanced player search - full data including projections
SearchNrlPlayers({
  searchTerm: "Smith", 
  teams: ["Panthers"],
  take: 10
})

3. User Context Integration

The MCP server integrates with your existing UserContext infrastructure:

  • Thread-safe: Uses AsyncLocal for proper context flow
  • Existing patterns: Leverages IRequireUserContext interface
  • Role-based access: Tools can check user roles for enhanced features

Security Features

Whitelisted Tools Only

  • No generic query access: Only specific, approved tools are exposed
  • Controlled data exposure: Each tool explicitly defines what data it returns
  • No SQL injection risk: Uses strongly-typed domain contracts

Authentication Controls

  • Optional authentication: Tools work for anonymous users with limited data
  • Enhanced data for authenticated users: Richer information when authenticated
  • Role-based features: Different data based on user roles
  • Secure token validation: Proper Clerk JWT validation

Data Access Controls

  • Domain layer integration: Direct access to existing contracts
  • No bypass mechanisms: Cannot access data outside defined tools
  • Audit logging: All tool usage is logged with user context

Development

Adding New Tools

  1. Create the tool method in appropriate tool class:
[McpServerTool, Description("Description of what this tool does")]
public async Task<MyResult> MyNewTool(
    [Description("Parameter description")] string parameter,
    string? bearerToken = null)
{
    // Optional authentication
    if (!string.IsNullOrWhiteSpace(bearerToken))
    {
        await this.authService.AuthenticateFromTokenAsync(bearerToken);
    }

    var currentUser = this.authService.GetCurrentUser();

    // Query domain contracts directly
    using var session = this.documentStore.LightweightSession();
    var results = await session.Query<SomeContract>()
        .Where(x => x.Property == parameter)
        .ToListAsync();

    // Shape response for LLM consumption
    return new MyResult { ... };
}
  1. Register the tool in Program.cs:
builder.Services.AddScoped<MyToolClass>();

Testing

# Build the MCP server
cd src/Apps/LBS.Ballr.McpServer
dotnet build

# Run with Aspire (includes MCP server)
cd src/Aspire/LBS.AspireHost  
dotnet run

Troubleshooting

Common Issues

  1. MCP Server not starting
  2. Check that ModelContextProtocol package is properly installed
  3. Verify all project references are correct
  4. Check Aspire host logs for startup errors

  5. Authentication failing

  6. Verify Clerk OAuth configuration in appsettings.json
  7. Check that Client ID and Client Secret are correct
  8. Ensure JWT token is valid and not expired

  9. Database connection issues

  10. Verify PostgreSQL is running (started by Aspire)
  11. Check connection string in Aspire configuration
  12. Ensure database migrations have been applied

  13. Tools not returning data

  14. Check that NrlSuperCoachPlayerStats feature is enabled
  15. Verify Marten projections are built
  16. Check database has SuperCoach data

Logs

MCP server logs are available through: - Aspire Dashboard: http://localhost:15888 (when running via Aspire) - Console output: When running the MCP server directly

Next Steps

  1. Implement remaining tools: Complete the stubbed CompetitionTools and SportingEventTools
  2. Enhanced authentication: Add role-based data filtering
  3. Performance optimization: Add caching for frequent queries
  4. Monitoring: Add telemetry and usage metrics
  5. Production deployment: Configure for production Clerk environment

References