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).
// 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.
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.
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.
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
Clone the example
git clone https://github.com/billai/billai.git
cd billai/examples/note-taker-mcpInstall dependencies
npm installConfigure environment
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.devRun the demo
npm startExpected Output
When you run the example, you'll see output showing which features are allowed and which require an upgrade:
═══════════════════════════════════════════════════════════════
📝 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_xxxKey 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.