Example App

Note Taker MCP Example

A complete example of an MCP server with BillAI access control. Learn how to gate premium features in your ChatGPT app.

Overview

The Note Taker MCP example demonstrates how to integrate BillAI into an MCP (Model Context Protocol) server. It provides a simple note-taking functionality with some features gated behind premium plans.

Free Features

  • Create notes
  • List notes
  • Get note by ID

Premium Features

  • Delete notes (requires "delete_notes")
  • AI summaries (requires "ai_summary")

How It Works

1. Feature Mapping

The example defines which MCP tools require which features. Tools not in the mapping are always allowed (free features).

Feature Mapping
// Map tools to their required features
const PREMIUM_FEATURES: Record<string, string> = {
  'delete_note': 'delete_notes',    // Requires "delete_notes" feature
  'summarize_note': 'ai_summary',   // Requires "ai_summary" feature
};

// Free features - no access check needed
const FREE_FEATURES = ['create_note', 'list_notes', 'get_note'];

2. AccessControl Setup

Initialize the AccessControl client with your API key and App ID.

Initialization
import { AccessControl } from '@billai/sdk';

const accessControl = new AccessControl({
  apiKey: process.env.BILLAI_API_KEY!,
  appId: process.env.BILLAI_APP_ID!,
  baseUrl: process.env.BILLAI_BASE_URL || 'https://api.billai.dev',
  debug: !!process.env.DEBUG,
});

3. Access Check Wrapper

A helper function checks if a user has access to a specific tool based on the feature mapping.

Access Check
async function checkFeatureAccess(
  userId: string,
  toolName: string
): Promise<{ allowed: true } | { allowed: false; result: AccessDeniedResult }> {
  // Free features - always allowed
  if (FREE_FEATURES.includes(toolName)) {
    return { allowed: true };
  }

  // Get the feature name for this tool
  const featureName = PREMIUM_FEATURES[toolName];
  
  // If no feature mapping, allow by default
  if (!featureName) {
    return { allowed: true };
  }

  // Check access with BillAI
  try {
    const result = await accessControl.check(userId, featureName);

    if (result.granted) {
      return { allowed: true };
    }

    // Access denied
    return {
      allowed: false,
      result: {
        error: 'access_denied',
        message: `⚠️ Upgrade required to use "${toolName}"`,
        upgradeUrl: result.upgradeUrl,
      },
    };
  } catch (error) {
    // Fail open - allow access if service is unavailable
    console.error('Access check failed:', error);
    return { allowed: true };
  }
}

4. Tool Handler

Before executing any tool, check if the user has access.

Tool Handler
async function handleToolCall(
  toolName: string,
  args: Record<string, unknown>,
  userId: string
): Promise<ToolResult> {
  // Check access before executing
  const accessCheck = await checkFeatureAccess(userId, toolName);
  
  if (!accessCheck.allowed) {
    const { result } = accessCheck;
    console.log(`❌ Access denied for user "${userId}" on tool "${toolName}"`);
    if (result.upgradeUrl) {
      console.log(`   Upgrade URL: ${result.upgradeUrl}`);
    }
    return result;
  }

  // Execute the tool
  switch (toolName) {
    case 'create_note':
      // ... handle create
      break;
    case 'delete_note':
      // ... handle delete (premium)
      break;
    // ... other tools
  }
}

Running the Example

1

Clone the example

Terminal
git clone https://github.com/billai/billai.git
cd billai/examples/note-taker-mcp
2

Install dependencies

Terminal
npm install
3

Configure environment

.env
BILLAI_API_KEY=sk_live_your_api_key
BILLAI_APP_ID=app_your_app_id
BILLAI_BASE_URL=http://localhost:3000  # or https://api.billai.dev
4

Run the demo

Terminal
npm start

Expected Output

When you run the example, you'll see output showing which features are allowed and which require an upgrade:

Example Output
═══════════════════════════════════════════════════════════════
  📝 Note Taker MCP Example with BillAI Access Control
═══════════════════════════════════════════════════════════════

Configuration:
  API Key: sk_live_xxx...
  App ID:  app_xxx
  Base URL: http://localhost:3000

─────────────────────────────────────────────────────────────────
  Feature Access Matrix:
─────────────────────────────────────────────────────────────────

  🆓 FREE Features (always allowed):
     • create_note
     • list_notes
     • get_note

  💎 PREMIUM Features (require plan features):
     • delete_note → requires "delete_notes"
     • summarize_note → requires "ai_summary"

─────────────────────────────────────────────────────────────────
  Running Demo...
─────────────────────────────────────────────────────────────────

👤 Test User ID: demo_user_1234567890

─── Test 1: Create Note (FREE) ───
✅ Note created: "My First Note" (ID: abc-123)

─── Test 2: List Notes (FREE) ───
📋 Listed 1 notes for user "demo_user_1234567890"

─── Test 3: Delete Note (PREMIUM) ───
❌ Access denied for user "demo_user_1234567890" on tool "delete_note"
   Upgrade URL: https://billai.dev/checkout/plan_xxx

─── Test 4: Summarize Note (PREMIUM) ───
❌ Access denied for user "demo_user_1234567890" on tool "summarize_note"
   Upgrade URL: https://billai.dev/checkout/plan_xxx

Key Patterns to Learn

Feature Mapping

Map your app's capabilities (tools, endpoints, actions) to BillAI feature names. This decouples your code from the billing logic.

Fail-Open Design

When the BillAI service is unavailable, allow access rather than blocking users. This ensures your app remains functional.

Upgrade URLs

Always pass the upgradeUrl back to users when access is denied. This provides a clear path to conversion.

Centralized Check

Run access checks before any operation, not scattered throughout your code. This makes it easy to audit and update access control.