@yawlabs/vend-mcp
v0.1.4
Published
Vend SDK for MCP servers — license key gating and usage metering in two lines
Maintainers
Readme
@yawlabs/vend-mcp
Payments for MCP servers. Add license key gating and usage metering in two lines of code.
npm install @yawlabs/vend-mcpQuick Start
import { vendAuth } from '@yawlabs/vend-mcp';
const guard = vendAuth({
apiKey: process.env.VEND_API_KEY!,
gates: {
search: 'free',
execute: 'pro',
deploy: 'business',
},
});
// In your tool handler:
if (!(await guard.canAccess('execute'))) {
return { content: [{ type: 'text', text: guard.upgradeMessage('execute') }] };
}
guard.recordUsage('execute');
// ... run the toolHow It Works
- Customers purchase a license key at your project's checkout page on vend.sh
- They set
VEND_LICENSE_KEYin their MCP client config - Your server uses
vendAuthto gate tools by tier and track usage
The SDK validates the key against the Vend API, caches the result, and lets you gate any tool to any tier.
API Reference
vendAuth(options): VendGuard
Creates a guard instance. Validation is lazy — no API calls until the first canAccess() or validate().
const guard = vendAuth({
// Required
apiKey: 'vend_xxx', // Your project API key from the dashboard
gates: { search: 'free', run: 'pro' }, // Tool name → minimum tier
// Optional
licenseKey: 'VEND-XXXX-...', // Override (default: process.env.VEND_LICENSE_KEY)
cacheTtl: 300, // Seconds to cache successful validations (default: 300)
errorCacheTtl: 30, // Seconds to cache failed validations (default: 30)
// Observability hooks (all optional)
onValidate: (result, fromCache) => {},
onActivate: (result) => {},
onUsage: (toolName, success) => {},
onError: (operation, error) => {},
});guard.canAccess(toolName): Promise<boolean>
Check if the current key can access a tool. Calls validate() internally (cached).
if (!(await guard.canAccess('deploy'))) {
return { content: [{ type: 'text', text: guard.upgradeMessage('deploy') }] };
}guard.validate(): Promise<ValidationResult>
Validate the license key against the Vend API. Results are cached per the TTL settings.
const result = await guard.validate();
// {
// valid: true,
// tier: 'pro',
// tierName: 'Pro',
// toolGates: { search: true, execute: true },
// requestLimit: 10000,
// activationsRemaining: 4,
// }On failure, reason tells you why:
| Reason | Meaning |
|--------|---------|
| no_license_key | No key provided |
| invalid_key_format | Key doesn't match VEND-XXXX-XXXX-XXXX-XXXX |
| invalid_api_key | Your project API key is wrong |
| key_not_found | Key doesn't exist or doesn't belong to your project |
| key_inactive / key_expired / key_revoked | Key is no longer valid |
| network_error | Couldn't reach vend.sh (falls back to cache if available) |
| validation_failed | Other API error |
guard.recordUsage(toolName): Promise<boolean>
Record a tool invocation. Returns true if recorded, false on failure. Safe to fire-and-forget:
// Fire-and-forget (won't block your handler)
guard.recordUsage('search');
// Or await for confirmation
const ok = await guard.recordUsage('search');guard.activate(instanceName, instanceId): Promise<ActivationResult>
Activate the key for a specific MCP client instance. Call this once on startup.
const result = await guard.activate('claude-desktop', crypto.randomUUID());
if (!result.activated) {
console.error(`Activation failed: ${result.reason}`);
// reason: 'activation_limit_reached' | 'key_not_found' | 'key_inactive' | ...
}guard.tier: TierGate
The current customer's tier (after validation). One of: 'free', 'starter', 'pro', 'business', 'enterprise'.
guard.upgradeMessage(toolName): string
A human-readable message explaining why a tool is gated and how to upgrade.
guard.licenseKey = 'VEND-...'
Update the license key at runtime. Clears the cache.
createToolGuard(guard)
Auto-gate and track usage for an MCP CallToolRequest handler:
import { vendAuth, createToolGuard } from '@yawlabs/vend-mcp';
const guard = vendAuth({ apiKey: '...', gates: { search: 'free', run: 'pro' } });
const gated = createToolGuard(guard);
server.setRequestHandler(
CallToolRequestSchema,
gated(async (request) => {
// This only runs if the key has access to request.params.name
// Usage is recorded automatically on success
return { content: [{ type: 'text', text: 'result' }] };
}),
);Tiers
Tiers are ordered. A pro key can access free and starter tools.
| Tier | Level |
|------|-------|
| free | 0 |
| starter | 1 |
| pro | 2 |
| business | 3 |
| enterprise | 4 |
Observability
Hook into every SDK operation for logging, metrics, or alerting:
const guard = vendAuth({
apiKey: process.env.VEND_API_KEY!,
gates: { search: 'free', execute: 'pro' },
onValidate: (result, fromCache) => {
console.log(`[vend] validate: valid=${result.valid} tier=${result.tier} cache=${fromCache}`);
},
onActivate: (result) => {
if (!result.activated) console.warn(`[vend] activation failed: ${result.reason}`);
},
onUsage: (toolName, success) => {
metrics.increment('vend.usage', { tool: toolName, ok: String(success) });
},
onError: (operation, error) => {
console.error(`[vend] ${operation} error:`, error);
sentry.captureException(error);
},
});Environment Variables
| Variable | Description |
|----------|-------------|
| VEND_LICENSE_KEY | Customer's license key (set by the end user) |
| VEND_API_URL | Override the API URL (default: https://vend.sh) |
License
MIT
