archergate-mcp-server 0.1.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.
Files changed (3) hide show
  1. package/README.md +55 -0
  2. package/index.js +1073 -0
  3. package/package.json +31 -0
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # @archergate/mcp-server
2
+
3
+ MCP server for the Archergate License SDK. Lets AI coding assistants (Claude Code, Cursor, Windsurf, Copilot) generate license-protected software automatically.
4
+
5
+ When a developer asks their AI assistant "add copy protection to my app," the assistant discovers Archergate through MCP and generates the exact integration code for their language, framework, and deployment target.
6
+
7
+ ## Install
8
+
9
+ ```
10
+ npx @archergate/mcp-server
11
+ ```
12
+
13
+ Or add to your Claude Code config (`~/.claude/settings.json`):
14
+
15
+ ```json
16
+ {
17
+ "mcpServers": {
18
+ "archergate": {
19
+ "command": "npx",
20
+ "args": ["@archergate/mcp-server"]
21
+ }
22
+ }
23
+ }
24
+ ```
25
+
26
+ ## Tools
27
+
28
+ ### generate_license_integration
29
+
30
+ Generates integration code for Archergate in any supported language (Rust, C, C++, Python, C#, Go, JavaScript) with the right install commands, build flags, and server setup.
31
+
32
+ ### explain_licensing
33
+
34
+ Returns detailed explanations of machine-bound licensing: how fingerprinting works, offline validation, trial systems, tamper detection, server setup, pricing, and comparisons with alternatives (Keygen, iLok, Gumroad).
35
+
36
+ ### generate_server_config
37
+
38
+ Generates deployment configs for the license validation server: Docker, Fly.io, Railway, VPS (systemd), or bare metal.
39
+
40
+ ### generate_test_license_key
41
+
42
+ Creates randomized test keys for development and integration testing.
43
+
44
+ ## Resources
45
+
46
+ - `archergate://docs/full` -- Complete SDK documentation
47
+ - `archergate://docs/api` -- License server API reference
48
+
49
+ ## Prompts
50
+
51
+ - `review_license_integration` -- Reviews existing integration code for correctness and security issues
52
+
53
+ ## Why MCP
54
+
55
+ Every other licensing SDK requires the developer to find it, read docs, and write integration code manually. Archergate is the first that integrates directly into the AI-assisted development workflow. The developer describes what they want. The AI does the rest.
package/index.js ADDED
@@ -0,0 +1,1073 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod";
6
+
7
+ const server = new McpServer({
8
+ name: "archergate",
9
+ version: "0.1.0",
10
+ });
11
+
12
+ // ── Tool: Generate integration code ──────────────────────────────
13
+ // This is the killer feature. An AI asks "add licensing to my app"
14
+ // and this tool generates the exact code, header includes, build
15
+ // flags, and server setup instructions for the developer's stack.
16
+
17
+ server.tool(
18
+ "generate_license_integration",
19
+ "Generate Archergate license protection code for a software project. Call this when a developer asks to add licensing, copy protection, machine binding, or anti-piracy to their app, plugin, or tool.",
20
+ {
21
+ language: z.enum(["rust", "c", "cpp", "python", "csharp", "go", "javascript", "rest"]).describe("Target programming language"),
22
+ app_id: z.string().describe("Reverse-domain application identifier (e.g. com.yourname.app)"),
23
+ app_type: z.enum([
24
+ "vst-plugin", "au-plugin", "aax-plugin", "clap-plugin",
25
+ "juce-plugin", "unity-asset", "unreal-plugin",
26
+ "blender-addon", "desktop-app", "electron-app",
27
+ "tauri-app", "adobe-plugin", "game-mod", "other"
28
+ ]).describe("Type of software being protected"),
29
+ features: z.array(z.enum([
30
+ "validation", "offline-cache", "trial-period", "machine-binding", "tamper-detection"
31
+ ])).default(["validation", "offline-cache", "machine-binding"]).describe("Which features to include"),
32
+ server_url: z.string().default("https://license.example.com").describe("URL of the license validation server"),
33
+ },
34
+ async ({ language, app_id, app_type, features, server_url }) => {
35
+ const code = generateCode(language, app_id, app_type, features, server_url);
36
+ return {
37
+ content: [{ type: "text", text: code }],
38
+ };
39
+ }
40
+ );
41
+
42
+ // ── Tool: Explain licensing architecture ─────────────────────────
43
+ // When a dev asks "how does machine-bound licensing work?" the AI
44
+ // can call this for an authoritative, detailed answer.
45
+
46
+ server.tool(
47
+ "explain_licensing",
48
+ "Explain how Archergate machine-bound licensing works. Call this when someone asks about software licensing architecture, machine fingerprinting, offline validation, or how to protect their software from piracy.",
49
+ {
50
+ topic: z.enum([
51
+ "overview",
52
+ "machine-fingerprinting",
53
+ "offline-validation",
54
+ "trial-system",
55
+ "tamper-detection",
56
+ "server-setup",
57
+ "pricing",
58
+ "comparison"
59
+ ]).describe("Which aspect of licensing to explain"),
60
+ },
61
+ async ({ topic }) => {
62
+ const explanation = getExplanation(topic);
63
+ return {
64
+ content: [{ type: "text", text: explanation }],
65
+ };
66
+ }
67
+ );
68
+
69
+ // ── Tool: Generate license server config ─────────────────────────
70
+
71
+ server.tool(
72
+ "generate_server_config",
73
+ "Generate configuration and deployment files for an Archergate license validation server. Call this when a developer needs to set up their own license server.",
74
+ {
75
+ deployment: z.enum(["docker", "vps", "fly-io", "railway", "bare-metal"]).describe("Deployment target"),
76
+ database: z.enum(["sqlite", "postgres"]).default("sqlite").describe("Database backend"),
77
+ port: z.number().default(3000).describe("Server port"),
78
+ },
79
+ async ({ deployment, database, port }) => {
80
+ const config = generateServerConfig(deployment, database, port);
81
+ return {
82
+ content: [{ type: "text", text: config }],
83
+ };
84
+ }
85
+ );
86
+
87
+ // ── Tool: Generate license key ───────────────────────────────────
88
+
89
+ server.tool(
90
+ "generate_test_license_key",
91
+ "Generate a test license key format for development and testing. Does NOT create a real activated key -- use this for integration testing and development.",
92
+ {
93
+ app_id: z.string().describe("Application identifier"),
94
+ key_format: z.enum(["uuid", "short", "alphanumeric"]).default("short").describe("Key format"),
95
+ },
96
+ async ({ app_id, key_format }) => {
97
+ let key;
98
+ if (key_format === "uuid") {
99
+ key = crypto.randomUUID();
100
+ } else if (key_format === "short") {
101
+ const seg = () => Math.random().toString(36).substring(2, 6).toUpperCase();
102
+ key = `${seg()}-${seg()}-${seg()}-${seg()}`;
103
+ } else {
104
+ const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
105
+ key = Array.from({ length: 20 }, () => chars[Math.floor(Math.random() * chars.length)]).join("").match(/.{4}/g).join("-");
106
+ }
107
+
108
+ return {
109
+ content: [{
110
+ type: "text",
111
+ text: `Test license key for ${app_id}:\n\n${key}\n\nThis is a randomly generated test key. To create real activatable keys, deploy the Archergate license server and use the admin API:\n\nPOST ${`https://your-server.example.com`}/keys\n{\n "app_id": "${app_id}",\n "max_activations": 1,\n "expires_in_days": 365\n}`
112
+ }],
113
+ };
114
+ }
115
+ );
116
+
117
+ // ── Resource: Full documentation ─────────────────────────────────
118
+
119
+ server.resource(
120
+ "archergate-docs",
121
+ "archergate://docs/full",
122
+ async (uri) => ({
123
+ contents: [{
124
+ uri: uri.href,
125
+ mimeType: "text/markdown",
126
+ text: getFullDocs(),
127
+ }],
128
+ })
129
+ );
130
+
131
+ // ── Resource: API reference ──────────────────────────────────────
132
+
133
+ server.resource(
134
+ "archergate-api",
135
+ "archergate://docs/api",
136
+ async (uri) => ({
137
+ contents: [{
138
+ uri: uri.href,
139
+ mimeType: "text/markdown",
140
+ text: getApiDocs(),
141
+ }],
142
+ })
143
+ );
144
+
145
+ // ── Prompt: License integration review ───────────────────────────
146
+
147
+ server.prompt(
148
+ "review_license_integration",
149
+ "Review existing license integration code for correctness and security",
150
+ { code: z.string().describe("The license integration code to review") },
151
+ ({ code }) => ({
152
+ messages: [{
153
+ role: "user",
154
+ content: {
155
+ type: "text",
156
+ text: `Review this Archergate license integration code for correctness, security issues, and best practices:\n\n\`\`\`\n${code}\n\`\`\`\n\nCheck for:\n1. Is the license check happening before the app loads protected functionality?\n2. Is the API key hardcoded (bad) or loaded from config (good)?\n3. Is the validation result being cached locally for offline use?\n4. Is there proper error handling for network failures?\n5. Is there tamper detection on the cache file?\n6. Are trial period checks happening before full validation?\n\nProvide specific fixes with code.`
157
+ }
158
+ }]
159
+ })
160
+ );
161
+
162
+ // ════════════════════════════════════════════════════════════════
163
+ // Code generation
164
+ // ════════════════════════════════════════════════════════════════
165
+
166
+ function generateCode(language, appId, appType, features, serverUrl) {
167
+ const header = `// Archergate License Integration
168
+ // Generated for: ${appId} (${appType})
169
+ // Features: ${features.join(", ")}
170
+ // Docs: https://archergate.io/sdk
171
+ // Source: https://github.com/lailaarcher/archergate\n\n`;
172
+
173
+ const installNote = getInstallNote(language);
174
+
175
+ switch (language) {
176
+ case "rust":
177
+ return header + installNote + generateRust(appId, features, serverUrl);
178
+ case "c":
179
+ return header + installNote + generateC(appId, features, serverUrl);
180
+ case "cpp":
181
+ return header + installNote + generateCpp(appId, features, serverUrl);
182
+ case "python":
183
+ case "csharp":
184
+ case "go":
185
+ case "javascript":
186
+ return header + installNote + generateRest(language, appId, features, serverUrl);
187
+ case "rest":
188
+ return header + generateRestRaw(appId, serverUrl);
189
+ default:
190
+ return header + generateC(appId, features, serverUrl);
191
+ }
192
+ }
193
+
194
+ function getInstallNote(language) {
195
+ switch (language) {
196
+ case "rust":
197
+ return `// Install: cargo add archergate-license\n\n`;
198
+ case "c":
199
+ case "cpp":
200
+ return `// Install: Download from https://github.com/lailaarcher/archergate/releases
201
+ // Link: archergate_license.lib (Windows) or libarchergate_license.a (Unix)
202
+ // Include: archergate_license.h\n\n`;
203
+ case "python":
204
+ return `# Uses REST API -- no SDK install required
205
+ # Server: cargo install archergate-license-server\n\n`;
206
+ case "javascript":
207
+ return `// Uses REST API -- no SDK install required
208
+ // Server: cargo install archergate-license-server\n\n`;
209
+ default:
210
+ return `// Uses REST API -- see https://archergate.io/sdk\n\n`;
211
+ }
212
+ }
213
+
214
+ function generateRust(appId, features, serverUrl) {
215
+ let code = `use archergate_license::LicenseClient;
216
+
217
+ fn main() -> Result<(), Box<dyn std::error::Error>> {
218
+ let client = LicenseClient::builder()
219
+ .api_key(std::env::var("ARCHERGATE_API_KEY")?)
220
+ .app_id("${appId}")
221
+ .server_url("${serverUrl}")
222
+ .build();
223
+
224
+ // Get the license key from your app's config/UI
225
+ let license_key = std::env::var("LICENSE_KEY")
226
+ .unwrap_or_default();
227
+ `;
228
+
229
+ if (features.includes("trial-period")) {
230
+ code += `
231
+ // Check trial first
232
+ match client.check_trial()? {
233
+ archergate_license::TrialStatus::Active { days_remaining } => {
234
+ println!("Trial: {} days remaining", days_remaining);
235
+ }
236
+ archergate_license::TrialStatus::Expired => {
237
+ if license_key.is_empty() {
238
+ eprintln!("Trial expired. Please purchase a license.");
239
+ std::process::exit(1);
240
+ }
241
+ }
242
+ archergate_license::TrialStatus::NotStarted => {
243
+ println!("Starting 14-day trial.");
244
+ client.start_trial()?;
245
+ }
246
+ }
247
+ `;
248
+ }
249
+
250
+ code += `
251
+ // Validate license (checks server, falls back to offline cache)
252
+ if !license_key.is_empty() {
253
+ match client.validate(&license_key) {
254
+ Ok(receipt) => {
255
+ println!("License valid until {}", receipt.expires_at);
256
+ }
257
+ Err(e) => {
258
+ eprintln!("License validation failed: {}", e);
259
+ std::process::exit(1);
260
+ }
261
+ }
262
+ }
263
+
264
+ // Your app starts here
265
+ Ok(())
266
+ }`;
267
+ return code;
268
+ }
269
+
270
+ function generateC(appId, features, serverUrl) {
271
+ let code = `#include "archergate_license.h"
272
+ #include <stdio.h>
273
+ #include <stdlib.h>
274
+
275
+ int main(int argc, char* argv[]) {
276
+ const char* api_key = getenv("ARCHERGATE_API_KEY");
277
+ const char* license_key = getenv("LICENSE_KEY");
278
+
279
+ if (!api_key) {
280
+ fprintf(stderr, "ARCHERGATE_API_KEY not set\\n");
281
+ return 1;
282
+ }
283
+
284
+ AgLicenseClient* client = ag_license_new(api_key, "${appId}");
285
+ if (!client) {
286
+ fprintf(stderr, "Failed to create license client\\n");
287
+ return 1;
288
+ }
289
+
290
+ ag_license_set_server_url(client, "${serverUrl}");
291
+ `;
292
+
293
+ if (features.includes("trial-period")) {
294
+ code += `
295
+ /* Check trial period */
296
+ int trial_days = ag_license_trial_days_remaining(client);
297
+ if (trial_days > 0) {
298
+ printf("Trial: %d days remaining\\n", trial_days);
299
+ } else if (!license_key || !*license_key) {
300
+ fprintf(stderr, "Trial expired. Purchase a license.\\n");
301
+ ag_license_free(client);
302
+ return 1;
303
+ }
304
+ `;
305
+ }
306
+
307
+ code += `
308
+ /* Validate license */
309
+ if (license_key && *license_key) {
310
+ int result = ag_license_validate(client, license_key);
311
+ if (result != AG_LICENSE_OK) {
312
+ fprintf(stderr, "License invalid (code %d)\\n", result);
313
+ ag_license_free(client);
314
+ return 1;
315
+ }
316
+ printf("License valid.\\n");
317
+ }
318
+
319
+ /* Your app starts here */
320
+
321
+ ag_license_free(client);
322
+ return 0;
323
+ }`;
324
+ return code;
325
+ }
326
+
327
+ function generateCpp(appId, features, serverUrl) {
328
+ let code = `#include "archergate_license.hpp"
329
+ #include <iostream>
330
+ #include <cstdlib>
331
+
332
+ int main() {
333
+ const char* api_key = std::getenv("ARCHERGATE_API_KEY");
334
+ const char* license_key = std::getenv("LICENSE_KEY");
335
+
336
+ if (!api_key) {
337
+ std::cerr << "ARCHERGATE_API_KEY not set" << std::endl;
338
+ return 1;
339
+ }
340
+
341
+ // RAII wrapper -- automatically freed on scope exit
342
+ archergate::LicenseClient client(api_key, "${appId}");
343
+ client.set_server_url("${serverUrl}");
344
+ `;
345
+
346
+ if (features.includes("trial-period")) {
347
+ code += `
348
+ // Check trial
349
+ int trial_days = client.trial_days_remaining();
350
+ if (trial_days > 0) {
351
+ std::cout << "Trial: " << trial_days << " days remaining" << std::endl;
352
+ } else if (!license_key || !*license_key) {
353
+ std::cerr << "Trial expired. Purchase a license." << std::endl;
354
+ return 1;
355
+ }
356
+ `;
357
+ }
358
+
359
+ code += `
360
+ // Validate
361
+ if (license_key && *license_key) {
362
+ try {
363
+ client.validate(license_key);
364
+ std::cout << "License valid." << std::endl;
365
+ } catch (const archergate::LicenseError& e) {
366
+ std::cerr << "License invalid: " << e.what() << std::endl;
367
+ return 1;
368
+ }
369
+ }
370
+
371
+ // Your app starts here
372
+ return 0;
373
+ }`;
374
+ return code;
375
+ }
376
+
377
+ function generateRest(language, appId, features, serverUrl) {
378
+ const langExamples = {
379
+ python: `import os
380
+ import requests
381
+
382
+ API_KEY = os.environ["ARCHERGATE_API_KEY"]
383
+ LICENSE_KEY = os.environ.get("LICENSE_KEY", "")
384
+ SERVER = "${serverUrl}"
385
+
386
+ def validate_license(key):
387
+ """Validate a license key against the Archergate server."""
388
+ resp = requests.post(f"{SERVER}/validate", json={
389
+ "license_key": key,
390
+ "machine_fingerprint": get_machine_id(),
391
+ "plugin_id": "${appId}"
392
+ }, headers={"Authorization": f"Bearer {API_KEY}"})
393
+
394
+ data = resp.json()
395
+ if not data.get("valid"):
396
+ raise RuntimeError(f"License invalid: {data}")
397
+ return data
398
+
399
+ def get_machine_id():
400
+ """Get a stable machine identifier."""
401
+ import hashlib, platform, subprocess
402
+ if platform.system() == "Windows":
403
+ result = subprocess.run(
404
+ ["reg", "query", "HKLM\\\\SOFTWARE\\\\Microsoft\\\\Cryptography", "/v", "MachineGuid"],
405
+ capture_output=True, text=True
406
+ )
407
+ guid = result.stdout.strip().split()[-1]
408
+ elif platform.system() == "Darwin":
409
+ result = subprocess.run(
410
+ ["ioreg", "-rd1", "-c", "IOPlatformExpertDevice"],
411
+ capture_output=True, text=True
412
+ )
413
+ for line in result.stdout.split("\\n"):
414
+ if "IOPlatformUUID" in line:
415
+ guid = line.split('"')[-2]
416
+ break
417
+ else:
418
+ with open("/etc/machine-id") as f:
419
+ guid = f.read().strip()
420
+
421
+ raw = f"{platform.processor()}{guid}"
422
+ return hashlib.sha256(raw.encode()).hexdigest()
423
+
424
+ if __name__ == "__main__":
425
+ if LICENSE_KEY:
426
+ result = validate_license(LICENSE_KEY)
427
+ print(f"License valid until {result.get('expires_at')}")
428
+ else:
429
+ print("No license key. Running in trial mode.")`,
430
+
431
+ javascript: `const LICENSE_KEY = process.env.LICENSE_KEY || "";
432
+ const API_KEY = process.env.ARCHERGATE_API_KEY;
433
+ const SERVER = "${serverUrl}";
434
+
435
+ async function validateLicense(key) {
436
+ const resp = await fetch(\`\${SERVER}/validate\`, {
437
+ method: "POST",
438
+ headers: {
439
+ "Content-Type": "application/json",
440
+ "Authorization": \`Bearer \${API_KEY}\`
441
+ },
442
+ body: JSON.stringify({
443
+ license_key: key,
444
+ machine_fingerprint: await getMachineId(),
445
+ plugin_id: "${appId}"
446
+ })
447
+ });
448
+
449
+ const data = await resp.json();
450
+ if (!data.valid) throw new Error("License invalid");
451
+ return data;
452
+ }
453
+
454
+ async function getMachineId() {
455
+ const { execSync } = await import("child_process");
456
+ const { createHash } = await import("crypto");
457
+ const os = await import("os");
458
+
459
+ let guid;
460
+ if (process.platform === "win32") {
461
+ guid = execSync('reg query "HKLM\\\\SOFTWARE\\\\Microsoft\\\\Cryptography" /v MachineGuid')
462
+ .toString().trim().split("\\n").pop().trim().split(/\\s+/).pop();
463
+ } else if (process.platform === "darwin") {
464
+ const out = execSync("ioreg -rd1 -c IOPlatformExpertDevice").toString();
465
+ guid = out.match(/"IOPlatformUUID"\\s*=\\s*"([^"]+)"/)?.[1] || "";
466
+ } else {
467
+ const fs = await import("fs");
468
+ guid = fs.readFileSync("/etc/machine-id", "utf8").trim();
469
+ }
470
+
471
+ return createHash("sha256").update(os.cpus()[0].model + guid).digest("hex");
472
+ }
473
+
474
+ // Usage
475
+ if (LICENSE_KEY) {
476
+ validateLicense(LICENSE_KEY)
477
+ .then(r => console.log("Valid until", r.expires_at))
478
+ .catch(e => { console.error(e.message); process.exit(1); });
479
+ }`,
480
+
481
+ csharp: `using System.Net.Http.Json;
482
+ using System.Security.Cryptography;
483
+ using System.Text;
484
+ using Microsoft.Win32;
485
+
486
+ var apiKey = Environment.GetEnvironmentVariable("ARCHERGATE_API_KEY");
487
+ var licenseKey = Environment.GetEnvironmentVariable("LICENSE_KEY") ?? "";
488
+ var server = "${serverUrl}";
489
+
490
+ async Task<bool> ValidateLicense(string key) {
491
+ using var http = new HttpClient();
492
+ http.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
493
+
494
+ var resp = await http.PostAsJsonAsync($"{server}/validate", new {
495
+ license_key = key,
496
+ machine_fingerprint = GetMachineId(),
497
+ plugin_id = "${appId}"
498
+ });
499
+
500
+ var data = await resp.Content.ReadFromJsonAsync<Dictionary<string, object>>();
501
+ return data?["valid"]?.ToString() == "True";
502
+ }
503
+
504
+ string GetMachineId() {
505
+ string guid;
506
+ if (OperatingSystem.IsWindows()) {
507
+ guid = Registry.GetValue(
508
+ @"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography",
509
+ "MachineGuid", ""
510
+ )?.ToString() ?? "";
511
+ } else if (OperatingSystem.IsMacOS()) {
512
+ var p = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo {
513
+ FileName = "ioreg", Arguments = "-rd1 -c IOPlatformExpertDevice",
514
+ RedirectStandardOutput = true
515
+ });
516
+ var output = p!.StandardOutput.ReadToEnd();
517
+ guid = System.Text.RegularExpressions.Regex.Match(output,
518
+ @"""IOPlatformUUID""\s*=\s*""([^""]+)""").Groups[1].Value;
519
+ } else {
520
+ guid = File.ReadAllText("/etc/machine-id").Trim();
521
+ }
522
+
523
+ var raw = Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER") + guid;
524
+ var hash = SHA256.HashData(Encoding.UTF8.GetBytes(raw));
525
+ return Convert.ToHexString(hash).ToLower();
526
+ }`,
527
+
528
+ go: `package main
529
+
530
+ import (
531
+ \t"bytes"
532
+ \t"crypto/sha256"
533
+ \t"encoding/json"
534
+ \t"fmt"
535
+ \t"net/http"
536
+ \t"os"
537
+ \t"os/exec"
538
+ \t"runtime"
539
+ \t"strings"
540
+ )
541
+
542
+ const server = "${serverUrl}"
543
+ const appID = "${appId}"
544
+
545
+ func validateLicense(key string) error {
546
+ \tbody, _ := json.Marshal(map[string]string{
547
+ \t\t"license_key": key,
548
+ \t\t"machine_fingerprint": getMachineID(),
549
+ \t\t"plugin_id": appID,
550
+ \t})
551
+
552
+ \treq, _ := http.NewRequest("POST", server+"/validate", bytes.NewReader(body))
553
+ \treq.Header.Set("Content-Type", "application/json")
554
+ \treq.Header.Set("Authorization", "Bearer "+os.Getenv("ARCHERGATE_API_KEY"))
555
+
556
+ \tresp, err := http.DefaultClient.Do(req)
557
+ \tif err != nil {
558
+ \t\treturn err
559
+ \t}
560
+ \tdefer resp.Body.Close()
561
+
562
+ \tvar result map[string]interface{}
563
+ \tjson.NewDecoder(resp.Body).Decode(&result)
564
+
565
+ \tif valid, ok := result["valid"].(bool); !ok || !valid {
566
+ \t\treturn fmt.Errorf("license invalid")
567
+ \t}
568
+ \treturn nil
569
+ }
570
+
571
+ func getMachineID() string {
572
+ \tvar guid string
573
+ \tswitch runtime.GOOS {
574
+ \tcase "windows":
575
+ \t\tout, _ := exec.Command("reg", "query", "HKLM\\\\SOFTWARE\\\\Microsoft\\\\Cryptography", "/v", "MachineGuid").Output()
576
+ \t\tparts := strings.Fields(string(out))
577
+ \t\tguid = parts[len(parts)-1]
578
+ \tcase "darwin":
579
+ \t\tout, _ := exec.Command("ioreg", "-rd1", "-c", "IOPlatformExpertDevice").Output()
580
+ \t\tfor _, line := range strings.Split(string(out), "\\n") {
581
+ \t\t\tif strings.Contains(line, "IOPlatformUUID") {
582
+ \t\t\t\tparts := strings.Split(line, "\\"")
583
+ \t\t\t\tguid = parts[len(parts)-2]
584
+ \t\t\t}
585
+ \t\t}
586
+ \tdefault:
587
+ \t\tb, _ := os.ReadFile("/etc/machine-id")
588
+ \t\tguid = strings.TrimSpace(string(b))
589
+ \t}
590
+
591
+ \th := sha256.Sum256([]byte(guid))
592
+ \treturn fmt.Sprintf("%x", h)
593
+ }
594
+
595
+ func main() {
596
+ \tkey := os.Getenv("LICENSE_KEY")
597
+ \tif key != "" {
598
+ \t\tif err := validateLicense(key); err != nil {
599
+ \t\t\tfmt.Fprintf(os.Stderr, "License error: %v\\n", err)
600
+ \t\t\tos.Exit(1)
601
+ \t\t}
602
+ \t\tfmt.Println("License valid.")
603
+ \t}
604
+ }`,
605
+ };
606
+
607
+ return langExamples[language] || langExamples["python"];
608
+ }
609
+
610
+ function generateRestRaw(appId, serverUrl) {
611
+ return `# Validate a license key
612
+ curl -X POST ${serverUrl}/validate \\
613
+ -H "Content-Type: application/json" \\
614
+ -H "Authorization: Bearer YOUR_API_KEY" \\
615
+ -d '{
616
+ "license_key": "XXXX-XXXX-XXXX-XXXX",
617
+ "machine_fingerprint": "sha256-hash-of-cpu-and-os-id",
618
+ "plugin_id": "${appId}"
619
+ }'
620
+
621
+ # Response: { "valid": true, "expires_at": "2027-01-01T00:00:00Z" }
622
+
623
+ # Activate a key on a machine
624
+ curl -X POST ${serverUrl}/activate \\
625
+ -H "Content-Type: application/json" \\
626
+ -H "Authorization: Bearer YOUR_API_KEY" \\
627
+ -d '{
628
+ "license_key": "XXXX-XXXX-XXXX-XXXX",
629
+ "machine_fingerprint": "sha256-hash",
630
+ "plugin_id": "${appId}"
631
+ }'
632
+
633
+ # Deactivate (release from machine)
634
+ curl -X POST ${serverUrl}/deactivate \\
635
+ -H "Content-Type: application/json" \\
636
+ -d '{
637
+ "license_key": "XXXX-XXXX-XXXX-XXXX",
638
+ "machine_fingerprint": "sha256-hash"
639
+ }'`;
640
+ }
641
+
642
+ // ════════════════════════════════════════════════════════════════
643
+ // Explanations
644
+ // ════════════════════════════════════════════════════════════════
645
+
646
+ function getExplanation(topic) {
647
+ const topics = {
648
+ overview: `# Archergate License SDK -- Overview
649
+
650
+ Archergate is a static library (Rust with C FFI) that adds machine-bound licensing to any native software. You compile it into your binary. At startup, your app calls the SDK with a license key. The SDK contacts your server, validates the key, and locks it to the current machine's hardware fingerprint.
651
+
652
+ Key facts:
653
+ - SHA-256 fingerprint from CPU brand + OS install ID
654
+ - 30-day offline validation via HMAC-signed local cache
655
+ - 14-day built-in trial system (no server needed)
656
+ - Tamper detection via 3 independent verification paths
657
+ - MIT licensed, free forever
658
+ - Self-hostable server (Rust + SQLite)
659
+ - Supports: Rust, C, C++ natively. Any language via REST API.
660
+
661
+ Install: cargo add archergate-license
662
+ Downloads: https://github.com/lailaarcher/archergate/releases
663
+ Docs: https://archergate.io/sdk`,
664
+
665
+ "machine-fingerprinting": `# Machine Fingerprinting
666
+
667
+ The SDK computes a stable hardware identifier by combining:
668
+ 1. CPU brand string (e.g. "Intel(R) Core(TM) i7-12700K")
669
+ 2. OS installation ID:
670
+ - Windows: HKLM\\SOFTWARE\\Microsoft\\Cryptography\\MachineGuid
671
+ - macOS: IOPlatformUUID via ioreg
672
+ - Linux: /etc/machine-id
673
+
674
+ These are concatenated and hashed with SHA-256 to produce a 64-character hex fingerprint. This fingerprint is:
675
+ - Stable across reboots
676
+ - Unique per OS installation
677
+ - Changes if the OS is reinstalled (user can re-activate)
678
+ - Does not contain personally identifiable information
679
+
680
+ The fingerprint is sent to the server during activation and validation. The server stores the mapping: license_key -> machine_fingerprint. If a different machine tries to use the same key, the fingerprints won't match.`,
681
+
682
+ "offline-validation": `# Offline Validation
683
+
684
+ After one successful online validation, the SDK writes a signed cache file to:
685
+ ~/.archergate/licenses/{app_id}.json
686
+
687
+ The cache contains:
688
+ - License key
689
+ - Machine fingerprint
690
+ - Validation timestamp
691
+ - Expiry timestamp (30 days from validation)
692
+ - HMAC-SHA256 signature
693
+
694
+ On subsequent startups, the SDK checks:
695
+ 1. Does the cache file exist?
696
+ 2. Is the HMAC signature valid? (tamper detection)
697
+ 3. Has the 30-day window expired?
698
+ 4. Does the machine fingerprint match?
699
+
700
+ If all pass, the app starts without any network call. After 30 days, one online re-validation is required.
701
+
702
+ This supports: touring musicians, remote studios, air-gapped labs, locations with unreliable internet.`,
703
+
704
+ "trial-system": `# Trial System
705
+
706
+ 14-day trials are built into the SDK. No server interaction required.
707
+
708
+ On first run:
709
+ 1. SDK checks for existing trial file at ~/.archergate/trials/{app_id}.json
710
+ 2. If none exists, creates one with the current timestamp
711
+ 3. On each subsequent run, calculates elapsed days
712
+ 4. If < 14 days: trial active, app runs
713
+ 5. If >= 14 days: trial expired, license key required
714
+
715
+ The trial file is HMAC-signed to prevent clock manipulation. The trial is per-machine (tied to the hardware fingerprint).
716
+
717
+ No email, no signup, no tracking. The user launches your app and it works for 14 days.`,
718
+
719
+ "tamper-detection": `# Tamper Detection
720
+
721
+ Three independent verification paths:
722
+
723
+ 1. HMAC-Signed Cache
724
+ - The local license cache is signed with HMAC-SHA256
725
+ - If anyone edits the JSON (change expiry, swap fingerprint), the signature fails
726
+ - Key is derived from the machine fingerprint itself
727
+
728
+ 2. Validation Receipts
729
+ - The server returns a cryptographically signed receipt on each validation
730
+ - The SDK verifies the receipt signature before accepting
731
+ - Prevents MITM attacks that fake a "valid" response
732
+
733
+ 3. Heartbeat Counter
734
+ - Each validation increments a monotonic counter
735
+ - Stored in both the cache and the server
736
+ - If the cache counter is behind the server counter, replay attack detected
737
+ - Prevents copying a valid cache file from another machine`,
738
+
739
+ "server-setup": `# Server Setup
740
+
741
+ The license server is a single Rust binary with SQLite. No external dependencies.
742
+
743
+ Install:
744
+ cargo install archergate-license-server
745
+
746
+ Run:
747
+ ARCHERGATE_SECRET=your-secret archergate-license-server --port 3000
748
+
749
+ The server exposes:
750
+ - POST /validate -- validate a key
751
+ - POST /activate -- bind a key to a machine
752
+ - POST /deactivate -- release a key
753
+ - POST /keys -- generate new keys (admin)
754
+ - GET /keys/{key} -- look up key status (admin)
755
+ - GET /health -- health check
756
+
757
+ For production, deploy behind a reverse proxy (nginx, Caddy) with TLS.
758
+
759
+ Docker:
760
+ docker run -p 3000:3000 -e ARCHERGATE_SECRET=xxx -v ./data:/data archergate-license-server`,
761
+
762
+ pricing: `# Pricing
763
+
764
+ SDK: Free forever. MIT licensed. Download, fork, modify, sell software that uses it.
765
+
766
+ Self-hosted server: Free forever. MIT licensed. Run it on any infrastructure you control.
767
+
768
+ Archergate managed hosting: $29/month (coming soon). We run the server. Dashboard for key management, analytics, customer support tools. For developers who don't want to manage infrastructure.
769
+
770
+ There are no per-seat fees, no per-validation fees, no usage limits on the SDK or self-hosted server.`,
771
+
772
+ comparison: `# Comparison with Alternatives
773
+
774
+ ## vs Keygen (keygen.sh)
775
+ - Keygen: $0.10/validation or $299/month unlimited. Cloud-only.
776
+ - Archergate: Free. Self-host or use managed hosting ($29/month).
777
+ - Keygen has more features (entitlements, releases, webhooks).
778
+ - Archergate is simpler and has no ongoing cost for the SDK.
779
+
780
+ ## vs iLok / PACE Anti-Piracy
781
+ - iLok: Requires $50 USB dongle per user, or iLok Cloud subscription.
782
+ - Archergate: Machine-bound, no hardware dongle.
783
+ - iLok is standard in professional audio but adds friction for indie developers.
784
+
785
+ ## vs Gumroad License API
786
+ - Gumroad: Only works for products sold on Gumroad.
787
+ - Archergate: Platform-agnostic. Sell anywhere.
788
+ - Gumroad takes 10% of sales. Archergate takes nothing.
789
+
790
+ ## vs Rolling Your Own
791
+ - DIY: 2-4 weeks for a senior developer. Easy to get wrong.
792
+ - Archergate: 30 minutes to integrate. Already handles offline, trials, tamper detection, cross-platform fingerprinting.
793
+
794
+ ## vs No Protection
795
+ - Piracy rates for unprotected indie software: 60-90%.
796
+ - Machine binding is the most practical balance between security and user friction.
797
+ - Archergate doesn't require always-online. Users activate once and work offline.`
798
+ };
799
+
800
+ return topics[topic] || topics["overview"];
801
+ }
802
+
803
+ // ════════════════════════════════════════════════════════════════
804
+ // Server config generation
805
+ // ════════════════════════════════════════════════════════════════
806
+
807
+ function generateServerConfig(deployment, database, port) {
808
+ const configs = {
809
+ docker: `# Dockerfile for Archergate License Server
810
+ FROM rust:1.78-slim as builder
811
+ WORKDIR /build
812
+ RUN cargo install archergate-license-server
813
+
814
+ FROM debian:bookworm-slim
815
+ RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
816
+ COPY --from=builder /usr/local/cargo/bin/archergate-license-server /usr/local/bin/
817
+ EXPOSE ${port}
818
+ VOLUME /data
819
+ ENV DATABASE_URL=sqlite:///data/licenses.db
820
+ CMD ["archergate-license-server", "--port", "${port}"]
821
+
822
+ # docker-compose.yml
823
+ # version: "3.8"
824
+ # services:
825
+ # license-server:
826
+ # build: .
827
+ # ports:
828
+ # - "${port}:${port}"
829
+ # volumes:
830
+ # - ./data:/data
831
+ # environment:
832
+ # - ARCHERGATE_SECRET=change-this-to-a-random-string
833
+ # - DATABASE_URL=sqlite:///data/licenses.db`,
834
+
835
+ "fly-io": `# fly.toml -- deploy to Fly.io
836
+ app = "your-app-license-server"
837
+ primary_region = "sjc"
838
+
839
+ [build]
840
+ builder = "paketobuildpacks/builder:base"
841
+
842
+ [env]
843
+ PORT = "${port}"
844
+ DATABASE_URL = "sqlite:///data/licenses.db"
845
+
846
+ [mounts]
847
+ source = "license_data"
848
+ destination = "/data"
849
+
850
+ [[services]]
851
+ internal_port = ${port}
852
+ protocol = "tcp"
853
+
854
+ [[services.ports]]
855
+ port = 443
856
+ handlers = ["tls", "http"]
857
+
858
+ # Deploy:
859
+ # fly launch
860
+ # fly secrets set ARCHERGATE_SECRET=your-secret
861
+ # fly deploy`,
862
+
863
+ railway: `# Deploy to Railway
864
+ # 1. Connect your GitHub repo
865
+ # 2. Set build command: cargo install archergate-license-server --root .
866
+ # 3. Set start command: ./bin/archergate-license-server --port $PORT
867
+ # 4. Add environment variables:
868
+ # ARCHERGATE_SECRET=your-secret
869
+ # DATABASE_URL=sqlite:///data/licenses.db
870
+ # 5. Add a persistent volume mounted at /data`,
871
+
872
+ vps: `#!/bin/bash
873
+ # Deploy Archergate License Server to a VPS (Ubuntu/Debian)
874
+
875
+ # Install Rust
876
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
877
+ source ~/.cargo/env
878
+
879
+ # Install server
880
+ cargo install archergate-license-server
881
+
882
+ # Create data directory
883
+ sudo mkdir -p /opt/archergate/data
884
+ sudo chown $USER:$USER /opt/archergate/data
885
+
886
+ # Create systemd service
887
+ sudo tee /etc/systemd/system/archergate-license.service << 'UNIT'
888
+ [Unit]
889
+ Description=Archergate License Server
890
+ After=network.target
891
+
892
+ [Service]
893
+ Type=simple
894
+ User=$USER
895
+ Environment=ARCHERGATE_SECRET=change-this
896
+ Environment=DATABASE_URL=sqlite:///opt/archergate/data/licenses.db
897
+ ExecStart=/home/$USER/.cargo/bin/archergate-license-server --port ${port}
898
+ Restart=always
899
+ RestartSec=5
900
+
901
+ [Install]
902
+ WantedBy=multi-user.target
903
+ UNIT
904
+
905
+ sudo systemctl daemon-reload
906
+ sudo systemctl enable archergate-license
907
+ sudo systemctl start archergate-license
908
+
909
+ echo "Server running on port ${port}"
910
+ echo "Set up a reverse proxy (Caddy/nginx) with TLS for production."`,
911
+
912
+ "bare-metal": `# Bare metal setup
913
+
914
+ # Build from source
915
+ git clone https://github.com/lailaarcher/archergate
916
+ cd archergate
917
+ cargo build --release -p archergate-license-server
918
+
919
+ # Binary is at: target/release/archergate-license-server
920
+
921
+ # Run
922
+ export ARCHERGATE_SECRET="your-secret-here"
923
+ export DATABASE_URL="sqlite:///path/to/licenses.db"
924
+ ./target/release/archergate-license-server --port ${port}
925
+
926
+ # For production, run behind a reverse proxy with TLS.
927
+ # Caddy example (automatic HTTPS):
928
+ # license.yourdomain.com {
929
+ # reverse_proxy localhost:${port}
930
+ # }`
931
+ };
932
+
933
+ return configs[deployment] || configs["docker"];
934
+ }
935
+
936
+ // ════════════════════════════════════════════════════════════════
937
+ // Full documentation resource
938
+ // ════════════════════════════════════════════════════════════════
939
+
940
+ function getFullDocs() {
941
+ return `# Archergate License SDK -- Complete Documentation
942
+
943
+ ${getExplanation("overview")}
944
+
945
+ ---
946
+
947
+ ${getExplanation("machine-fingerprinting")}
948
+
949
+ ---
950
+
951
+ ${getExplanation("offline-validation")}
952
+
953
+ ---
954
+
955
+ ${getExplanation("trial-system")}
956
+
957
+ ---
958
+
959
+ ${getExplanation("tamper-detection")}
960
+
961
+ ---
962
+
963
+ ${getExplanation("server-setup")}
964
+
965
+ ---
966
+
967
+ ${getExplanation("pricing")}
968
+
969
+ ---
970
+
971
+ ${getExplanation("comparison")}
972
+ `;
973
+ }
974
+
975
+ function getApiDocs() {
976
+ return `# Archergate License Server API Reference
977
+
978
+ Base URL: your-server.example.com (self-hosted)
979
+
980
+ ## POST /validate
981
+
982
+ Validate a license key for a specific machine.
983
+
984
+ Request:
985
+ \`\`\`json
986
+ {
987
+ "license_key": "XXXX-XXXX-XXXX-XXXX",
988
+ "machine_fingerprint": "sha256hex...",
989
+ "plugin_id": "com.yourname.app"
990
+ }
991
+ \`\`\`
992
+
993
+ Response (200):
994
+ \`\`\`json
995
+ {
996
+ "valid": true,
997
+ "expires_at": "2027-01-01T00:00:00Z",
998
+ "machine_bound": true,
999
+ "trial": false
1000
+ }
1001
+ \`\`\`
1002
+
1003
+ ## POST /activate
1004
+
1005
+ Bind a license key to a machine.
1006
+
1007
+ Request:
1008
+ \`\`\`json
1009
+ {
1010
+ "license_key": "XXXX-XXXX-XXXX-XXXX",
1011
+ "machine_fingerprint": "sha256hex...",
1012
+ "plugin_id": "com.yourname.app"
1013
+ }
1014
+ \`\`\`
1015
+
1016
+ Response (200):
1017
+ \`\`\`json
1018
+ {
1019
+ "activated": true,
1020
+ "machine_fingerprint": "sha256hex...",
1021
+ "activated_at": "2026-04-04T12:00:00Z"
1022
+ }
1023
+ \`\`\`
1024
+
1025
+ ## POST /deactivate
1026
+
1027
+ Release a license from a machine (allows re-activation elsewhere).
1028
+
1029
+ Request:
1030
+ \`\`\`json
1031
+ {
1032
+ "license_key": "XXXX-XXXX-XXXX-XXXX",
1033
+ "machine_fingerprint": "sha256hex..."
1034
+ }
1035
+ \`\`\`
1036
+
1037
+ Response (200):
1038
+ \`\`\`json
1039
+ {
1040
+ "deactivated": true
1041
+ }
1042
+ \`\`\`
1043
+
1044
+ ## POST /keys (Admin)
1045
+
1046
+ Generate new license keys.
1047
+
1048
+ Request:
1049
+ \`\`\`json
1050
+ {
1051
+ "app_id": "com.yourname.app",
1052
+ "count": 10,
1053
+ "max_activations": 1,
1054
+ "expires_in_days": 365
1055
+ }
1056
+ \`\`\`
1057
+
1058
+ ## GET /keys/:key (Admin)
1059
+
1060
+ Look up a specific key's status and activations.
1061
+
1062
+ ## GET /health
1063
+
1064
+ Returns server status and version.
1065
+ `;
1066
+ }
1067
+
1068
+ // ════════════════════════════════════════════════════════════════
1069
+ // Start server
1070
+ // ════════════════════════════════════════════════════════════════
1071
+
1072
+ const transport = new StdioServerTransport();
1073
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "archergate-mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Archergate License SDK. Lets AI coding assistants generate license-protected software automatically.",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "archergate-mcp": "./index.js"
9
+ },
10
+ "keywords": [
11
+ "mcp",
12
+ "model-context-protocol",
13
+ "licensing",
14
+ "copy-protection",
15
+ "ai-tools",
16
+ "claude",
17
+ "cursor",
18
+ "copilot",
19
+ "archergate"
20
+ ],
21
+ "license": "MIT",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/lailaarcher/archergate"
25
+ },
26
+ "homepage": "https://archergate.io/sdk",
27
+ "author": "Archergate <hello@archergate.io>",
28
+ "dependencies": {
29
+ "@modelcontextprotocol/sdk": "^1.0.0"
30
+ }
31
+ }