@jenny-ouyang/btl-license-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { validateLicense, cacheLicense, getCachedLicense, clearCachedLicense, PLUGIN_IDS, } from "./license.js";
6
+ const server = new McpServer({
7
+ name: "btl-license-mcp",
8
+ version: "1.0.0",
9
+ });
10
+ server.tool("validate_license", "Validate a Gumroad license key for a Build to Launch premium plugin. Call this at the start of any premium skill. Returns valid/invalid and caches the result so the user doesn't have to re-enter their key.", {
11
+ plugin_id: z
12
+ .enum(["newsletter-engine", "ai-builder-toolkit"])
13
+ .describe("The plugin ID to validate the license for"),
14
+ license_key: z
15
+ .string()
16
+ .optional()
17
+ .describe("The Gumroad license key. If omitted, checks the local cache first."),
18
+ }, async ({ plugin_id, license_key }) => {
19
+ // Check cache first if no key provided
20
+ if (!license_key) {
21
+ const cached = getCachedLicense(plugin_id);
22
+ if (cached) {
23
+ return {
24
+ content: [
25
+ {
26
+ type: "text",
27
+ text: `✅ License active for ${plugin_id}\nAccount: ${cached.email}\n\nYou're good to go — proceed with the skill.`,
28
+ },
29
+ ],
30
+ };
31
+ }
32
+ return {
33
+ content: [
34
+ {
35
+ type: "text",
36
+ text: `🔒 ${plugin_id} is a premium plugin.\n\nTo unlock it:\n1. Purchase at https://buildtolaunch.gumroad.com/l/${plugin_id}\n2. Copy your license key from the Gumroad receipt email\n3. Run this tool again with your license_key`,
37
+ },
38
+ ],
39
+ };
40
+ }
41
+ const result = await validateLicense(plugin_id, license_key);
42
+ if (!result.valid) {
43
+ return {
44
+ content: [
45
+ {
46
+ type: "text",
47
+ text: `❌ License invalid for ${plugin_id}\n\nReason: ${result.error}\n\nGet access at https://buildtolaunch.gumroad.com/l/${plugin_id}`,
48
+ },
49
+ ],
50
+ };
51
+ }
52
+ cacheLicense(plugin_id, license_key, result.email);
53
+ return {
54
+ content: [
55
+ {
56
+ type: "text",
57
+ text: `✅ License validated and saved for ${plugin_id}\nAccount: ${result.email}\n\nYour key is cached — you won't need to enter it again on this machine.\n\nProceed with the skill.`,
58
+ },
59
+ ],
60
+ };
61
+ });
62
+ server.tool("check_license_status", "Check which Build to Launch premium plugins are currently unlocked on this machine.", {}, async () => {
63
+ const lines = ["Build to Launch Premium Plugin Status\n"];
64
+ for (const pluginId of PLUGIN_IDS) {
65
+ const cached = getCachedLicense(pluginId);
66
+ if (cached) {
67
+ lines.push(`✅ ${pluginId} — active (${cached.email})`);
68
+ }
69
+ else {
70
+ lines.push(`🔒 ${pluginId} — not activated`);
71
+ }
72
+ }
73
+ lines.push("\nTo activate a locked plugin, run validate_license with your license key.");
74
+ return { content: [{ type: "text", text: lines.join("\n") }] };
75
+ });
76
+ server.tool("revoke_license", "Remove a cached license from this machine. Use if you're moving to a new machine or troubleshooting.", {
77
+ plugin_id: z
78
+ .enum(["newsletter-engine", "ai-builder-toolkit"])
79
+ .describe("The plugin ID to revoke"),
80
+ }, async ({ plugin_id }) => {
81
+ clearCachedLicense(plugin_id);
82
+ return {
83
+ content: [
84
+ {
85
+ type: "text",
86
+ text: `License for ${plugin_id} removed from this machine.`,
87
+ },
88
+ ],
89
+ };
90
+ });
91
+ const transport = new StdioServerTransport();
92
+ await server.connect(transport);
@@ -0,0 +1,14 @@
1
+ export declare const PLUGIN_IDS: readonly ["newsletter-engine", "ai-builder-toolkit"];
2
+ export type PluginId = typeof PLUGIN_IDS[number];
3
+ export declare function getCachedLicense(pluginId: string): {
4
+ key: string;
5
+ email: string;
6
+ } | null;
7
+ export declare function cacheLicense(pluginId: string, key: string, email: string): void;
8
+ export declare function clearCachedLicense(pluginId: string): void;
9
+ export interface ValidationResult {
10
+ valid: boolean;
11
+ email?: string;
12
+ error?: string;
13
+ }
14
+ export declare function validateLicense(pluginId: string, licenseKey: string): Promise<ValidationResult>;
@@ -0,0 +1,57 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ const CACHE_DIR = join(homedir(), ".btl-license");
5
+ const CACHE_FILE = join(CACHE_DIR, "licenses.json");
6
+ const LICENSE_API = "https://resources.buildtolaunch.ai/api/licenses/verify";
7
+ export const PLUGIN_IDS = ["newsletter-engine", "ai-builder-toolkit"];
8
+ function readCache() {
9
+ if (!existsSync(CACHE_FILE))
10
+ return {};
11
+ try {
12
+ return JSON.parse(readFileSync(CACHE_FILE, "utf-8"));
13
+ }
14
+ catch {
15
+ return {};
16
+ }
17
+ }
18
+ function writeCache(cache) {
19
+ if (!existsSync(CACHE_DIR)) {
20
+ mkdirSync(CACHE_DIR, { recursive: true });
21
+ }
22
+ writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
23
+ }
24
+ export function getCachedLicense(pluginId) {
25
+ const cache = readCache();
26
+ const entry = cache[pluginId];
27
+ if (!entry)
28
+ return null;
29
+ return { key: entry.key, email: entry.email };
30
+ }
31
+ export function cacheLicense(pluginId, key, email) {
32
+ const cache = readCache();
33
+ cache[pluginId] = { key, email, validatedAt: new Date().toISOString() };
34
+ writeCache(cache);
35
+ }
36
+ export function clearCachedLicense(pluginId) {
37
+ const cache = readCache();
38
+ delete cache[pluginId];
39
+ writeCache(cache);
40
+ }
41
+ export async function validateLicense(pluginId, licenseKey) {
42
+ try {
43
+ const response = await fetch(LICENSE_API, {
44
+ method: "POST",
45
+ headers: { "Content-Type": "application/json" },
46
+ body: JSON.stringify({ license_key: licenseKey, plugin_id: pluginId }),
47
+ });
48
+ const data = (await response.json());
49
+ if (!data.valid) {
50
+ return { valid: false, error: data.error ?? "Invalid license key." };
51
+ }
52
+ return { valid: true, email: data.email };
53
+ }
54
+ catch {
55
+ return { valid: false, error: "Could not reach license server. Check your internet connection." };
56
+ }
57
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@jenny-ouyang/btl-license-mcp",
3
+ "version": "1.0.0",
4
+ "description": "License validation MCP for Build to Launch premium plugins — validates Gumroad license keys and unlocks premium plugin content.",
5
+ "keywords": ["mcp", "claude", "claude-code", "build-to-launch", "license"],
6
+ "author": "Build to Launch",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/Build-to-Launch/build-to-launch-plugins"
11
+ },
12
+ "type": "module",
13
+ "main": "dist/index.js",
14
+ "bin": {
15
+ "btl-license-mcp": "dist/index.js"
16
+ },
17
+ "files": ["dist"],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "node --loader ts-node/esm src/index.ts",
21
+ "start": "node dist/index.js"
22
+ },
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.0.0",
25
+ "zod": "^3.25.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^20.0.0",
29
+ "typescript": "^5.4.0",
30
+ "ts-node": "^10.9.0"
31
+ }
32
+ }