paygate-mcp 5.1.0 → 5.3.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,129 @@
1
+ "use strict";
2
+ /**
3
+ * KeyTemplateManager — Named templates for API key creation.
4
+ *
5
+ * Templates define reusable presets for key creation (credits, ACL, quotas, etc.).
6
+ * Instead of passing all options every time, use `template: "free-tier"` in POST /keys.
7
+ *
8
+ * Features:
9
+ * - CRUD: create, update, list, get, delete templates
10
+ * - File persistence (-templates.json alongside state file)
11
+ * - Templates define: credits, allowedTools, deniedTools, quota, ipAllowlist,
12
+ * spendingLimit, tags, namespace, expiryTtlSeconds, autoTopup
13
+ * - Max 100 templates
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.KeyTemplateManager = void 0;
17
+ const fs_1 = require("fs");
18
+ const path_1 = require("path");
19
+ // ─── Manager Class ────────────────────────────────────────────────────────────
20
+ const MAX_TEMPLATES = 100;
21
+ class KeyTemplateManager {
22
+ templates = new Map();
23
+ filePath;
24
+ constructor(filePath) {
25
+ this.filePath = filePath || null;
26
+ if (this.filePath) {
27
+ this.loadFromFile();
28
+ }
29
+ }
30
+ /**
31
+ * Create or update a template.
32
+ */
33
+ set(name, data) {
34
+ // Validate name
35
+ const sanitized = name.trim().slice(0, 50);
36
+ if (!/^[a-zA-Z0-9_-]+$/.test(sanitized)) {
37
+ return { success: false, error: 'Template name must contain only letters, numbers, hyphens, and underscores' };
38
+ }
39
+ const existing = this.templates.get(sanitized);
40
+ const now = new Date().toISOString();
41
+ const template = {
42
+ name: sanitized,
43
+ description: String(data.description || existing?.description || '').slice(0, 500),
44
+ credits: Math.max(0, Math.floor(Number(data.credits ?? existing?.credits ?? 100))),
45
+ allowedTools: Array.isArray(data.allowedTools) ? data.allowedTools.filter(t => typeof t === 'string').slice(0, 100) : (existing?.allowedTools || []),
46
+ deniedTools: Array.isArray(data.deniedTools) ? data.deniedTools.filter(t => typeof t === 'string').slice(0, 100) : (existing?.deniedTools || []),
47
+ quota: data.quota !== undefined ? data.quota : existing?.quota,
48
+ ipAllowlist: Array.isArray(data.ipAllowlist) ? data.ipAllowlist.filter(t => typeof t === 'string').slice(0, 100) : (existing?.ipAllowlist || []),
49
+ spendingLimit: Math.max(0, Number(data.spendingLimit ?? existing?.spendingLimit ?? 0)),
50
+ tags: typeof data.tags === 'object' && data.tags !== null ? data.tags : (existing?.tags || {}),
51
+ namespace: String(data.namespace || existing?.namespace || 'default').trim().toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 50) || 'default',
52
+ expiryTtlSeconds: Math.max(0, Math.floor(Number(data.expiryTtlSeconds ?? existing?.expiryTtlSeconds ?? 0))),
53
+ autoTopup: data.autoTopup !== undefined ? data.autoTopup : existing?.autoTopup,
54
+ createdAt: existing?.createdAt || now,
55
+ updatedAt: now,
56
+ };
57
+ // Check limit (only for new templates)
58
+ if (!existing && this.templates.size >= MAX_TEMPLATES) {
59
+ return { success: false, error: `Maximum ${MAX_TEMPLATES} templates reached` };
60
+ }
61
+ this.templates.set(sanitized, template);
62
+ this.saveToFile();
63
+ return { success: true, template };
64
+ }
65
+ /**
66
+ * Get a template by name.
67
+ */
68
+ get(name) {
69
+ return this.templates.get(name) || null;
70
+ }
71
+ /**
72
+ * Delete a template.
73
+ */
74
+ delete(name) {
75
+ const existed = this.templates.delete(name);
76
+ if (existed)
77
+ this.saveToFile();
78
+ return existed;
79
+ }
80
+ /**
81
+ * List all templates.
82
+ */
83
+ list() {
84
+ return Array.from(this.templates.values()).sort((a, b) => a.name.localeCompare(b.name));
85
+ }
86
+ /**
87
+ * Get template count.
88
+ */
89
+ get count() {
90
+ return this.templates.size;
91
+ }
92
+ // ─── File Persistence ──────────────────────────────────────────────────────
93
+ saveToFile() {
94
+ if (!this.filePath)
95
+ return;
96
+ const data = Array.from(this.templates.entries());
97
+ const json = JSON.stringify(data, null, 2);
98
+ const tmpPath = this.filePath + '.tmp';
99
+ try {
100
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(this.filePath), { recursive: true });
101
+ (0, fs_1.writeFileSync)(tmpPath, json, 'utf-8');
102
+ (0, fs_1.renameSync)(tmpPath, this.filePath);
103
+ }
104
+ catch (err) {
105
+ console.error(`[paygate] Failed to save templates: ${err.message}`);
106
+ }
107
+ }
108
+ loadFromFile() {
109
+ if (!this.filePath || !(0, fs_1.existsSync)(this.filePath))
110
+ return;
111
+ try {
112
+ const json = (0, fs_1.readFileSync)(this.filePath, 'utf-8');
113
+ const data = JSON.parse(json);
114
+ if (!Array.isArray(data))
115
+ return;
116
+ for (const [name, template] of data) {
117
+ if (name && template && typeof template.name === 'string') {
118
+ this.templates.set(name, template);
119
+ }
120
+ }
121
+ console.log(`[paygate] Loaded ${this.templates.size} template(s) from ${this.filePath}`);
122
+ }
123
+ catch (err) {
124
+ console.error(`[paygate] Failed to load templates: ${err.message}`);
125
+ }
126
+ }
127
+ }
128
+ exports.KeyTemplateManager = KeyTemplateManager;
129
+ //# sourceMappingURL=key-templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-templates.js","sourceRoot":"","sources":["../src/key-templates.ts"],"names":[],"mappings":"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"}
package/dist/server.d.ts CHANGED
@@ -31,6 +31,7 @@ import { AdminKeyManager } from './admin-keys';
31
31
  import { PluginManager, PayGatePlugin } from './plugin';
32
32
  import { KeyGroupManager } from './groups';
33
33
  import { ExpiryScanner } from './expiry-scanner';
34
+ import { KeyTemplateManager } from './key-templates';
34
35
  /** Union type for both proxy backends */
35
36
  type ProxyBackend = McpProxy | HttpMcpProxy;
36
37
  export declare class PayGateServer {
@@ -71,6 +72,8 @@ export declare class PayGateServer {
71
72
  readonly groups: KeyGroupManager;
72
73
  /** Background key expiry scanner */
73
74
  readonly expiryScanner: ExpiryScanner;
75
+ /** Key template manager for reusable key presets */
76
+ readonly templates: KeyTemplateManager;
74
77
  /** Server start time (ms since epoch) */
75
78
  private readonly startedAt;
76
79
  /** Whether the server is draining (shutting down gracefully) */
@@ -214,11 +217,9 @@ export declare class PayGateServer {
214
217
  private handleListAdminKeys;
215
218
  private handleCreateAdminKey;
216
219
  private handleRevokeAdminKey;
217
- /**
218
- * Sync a key mutation to Redis. Call after any local KeyStore mutation
219
- * (setAcl, setExpiry, setQuota, setTags, setIpAllowlist, setSpendingLimit).
220
- * Fire-and-forget: errors logged, never thrown.
221
- */
220
+ private handleListTemplates;
221
+ private handleCreateTemplate;
222
+ private handleDeleteTemplate;
222
223
  /**
223
224
  * Route admin webhook events through the WebhookRouter (for filter rules) or direct emitter.
224
225
  */
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/dist/server.js CHANGED
@@ -84,6 +84,7 @@ const admin_keys_1 = require("./admin-keys");
84
84
  const plugin_1 = require("./plugin");
85
85
  const groups_1 = require("./groups");
86
86
  const expiry_scanner_1 = require("./expiry-scanner");
87
+ const key_templates_1 = require("./key-templates");
87
88
  /** Max request body size: 1MB */
88
89
  const MAX_BODY_SIZE = 1_048_576;
89
90
  class PayGateServer {
@@ -124,6 +125,8 @@ class PayGateServer {
124
125
  groups;
125
126
  /** Background key expiry scanner */
126
127
  expiryScanner;
128
+ /** Key template manager for reusable key presets */
129
+ templates;
127
130
  /** Server start time (ms since epoch) */
128
131
  startedAt = Date.now();
129
132
  /** Whether the server is draining (shutting down gracefully) */
@@ -260,6 +263,12 @@ class PayGateServer {
260
263
  thresholdSeconds: warning.thresholdSeconds,
261
264
  });
262
265
  };
266
+ // Key template manager for reusable key creation presets
267
+ const templatesStatePath = statePath ? statePath.replace(/\.json$/, '-templates.json') : undefined;
268
+ this.templates = new key_templates_1.KeyTemplateManager(templatesStatePath);
269
+ this.metrics.registerGauge('paygate_templates_total', 'Number of key templates', () => {
270
+ return this.templates.count;
271
+ });
263
272
  // Scoped token manager (uses bootstrap admin key as signing secret, padded to min length)
264
273
  const tokenSecret = this.bootstrapAdminKey.length >= 8
265
274
  ? this.bootstrapAdminKey
@@ -409,6 +418,14 @@ class PayGateServer {
409
418
  return this.handleKeyUsage(req, res);
410
419
  case '/keys/expiring':
411
420
  return this.handleKeysExpiring(req, res);
421
+ case '/keys/templates':
422
+ if (req.method === 'GET')
423
+ return this.handleListTemplates(req, res);
424
+ if (req.method === 'POST')
425
+ return this.handleCreateTemplate(req, res);
426
+ break;
427
+ case '/keys/templates/delete':
428
+ return this.handleDeleteTemplate(req, res);
412
429
  case '/topup':
413
430
  return this.handleTopUp(req, res);
414
431
  case '/keys/transfer':
@@ -934,6 +951,8 @@ class PayGateServer {
934
951
  autoTopup: 'POST /keys/auto-topup — Configure auto-topup for a key (requires X-Admin-Key)',
935
952
  keyUsage: 'GET /keys/usage?key=... — Per-key usage breakdown (requires X-Admin-Key)',
936
953
  keysExpiring: 'GET /keys/expiring?within=86400 — List keys expiring within N seconds (requires X-Admin-Key)',
954
+ keyTemplates: 'GET /keys/templates — List key templates + POST to create/update (requires X-Admin-Key)',
955
+ deleteTemplate: 'POST /keys/templates/delete — Delete a key template (requires X-Admin-Key)',
937
956
  pricing: 'GET /pricing — Tool pricing breakdown (public)',
938
957
  mcpPayment: 'GET /.well-known/mcp-payment — Payment metadata (SEP-2007)',
939
958
  audit: 'GET /audit — Query audit log (requires X-Admin-Key)',
@@ -1045,14 +1064,24 @@ class PayGateServer {
1045
1064
  res.end(JSON.stringify({ error: 'Invalid JSON' }));
1046
1065
  return;
1047
1066
  }
1067
+ // Resolve template defaults (explicit params override template values)
1068
+ let tpl = null;
1069
+ if (params.template) {
1070
+ tpl = this.templates.get(params.template);
1071
+ if (!tpl) {
1072
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1073
+ res.end(JSON.stringify({ error: `Template "${params.template}" not found` }));
1074
+ return;
1075
+ }
1076
+ }
1048
1077
  const name = String(params.name || 'unnamed').slice(0, 200);
1049
- const credits = Math.max(0, Math.floor(Number(params.credits) || 100));
1078
+ const credits = Math.max(0, Math.floor(Number(params.credits ?? tpl?.credits ?? 100)));
1050
1079
  if (credits <= 0) {
1051
1080
  res.writeHead(400, { 'Content-Type': 'application/json' });
1052
1081
  res.end(JSON.stringify({ error: 'Credits must be a positive integer' }));
1053
1082
  return;
1054
1083
  }
1055
- // Calculate expiry: expiresIn (seconds) takes priority over expiresAt (ISO date)
1084
+ // Calculate expiry: expiresIn (seconds) takes priority over expiresAt (ISO date), template TTL is fallback
1056
1085
  let expiresAt = null;
1057
1086
  if (params.expiresIn && Number(params.expiresIn) > 0) {
1058
1087
  expiresAt = new Date(Date.now() + Number(params.expiresIn) * 1000).toISOString();
@@ -1060,7 +1089,10 @@ class PayGateServer {
1060
1089
  else if (params.expiresAt) {
1061
1090
  expiresAt = String(params.expiresAt);
1062
1091
  }
1063
- // Parse quota if provided
1092
+ else if (tpl && tpl.expiryTtlSeconds > 0) {
1093
+ expiresAt = new Date(Date.now() + tpl.expiryTtlSeconds * 1000).toISOString();
1094
+ }
1095
+ // Parse quota: explicit params > template > undefined
1064
1096
  let quota = undefined;
1065
1097
  if (params.quota) {
1066
1098
  quota = {
@@ -1070,15 +1102,28 @@ class PayGateServer {
1070
1102
  monthlyCreditLimit: Math.max(0, Math.floor(Number(params.quota.monthlyCreditLimit) || 0)),
1071
1103
  };
1072
1104
  }
1105
+ else if (tpl?.quota) {
1106
+ quota = { ...tpl.quota };
1107
+ }
1073
1108
  const record = this.gate.store.createKey(name, credits, {
1074
- allowedTools: params.allowedTools,
1075
- deniedTools: params.deniedTools,
1109
+ allowedTools: params.allowedTools || (tpl ? [...tpl.allowedTools] : undefined),
1110
+ deniedTools: params.deniedTools || (tpl ? [...tpl.deniedTools] : undefined),
1076
1111
  expiresAt,
1077
1112
  quota,
1078
- tags: params.tags,
1079
- ipAllowlist: params.ipAllowlist,
1080
- namespace: params.namespace,
1113
+ tags: params.tags || (tpl ? { ...tpl.tags } : undefined),
1114
+ ipAllowlist: params.ipAllowlist || (tpl ? [...tpl.ipAllowlist] : undefined),
1115
+ namespace: params.namespace || tpl?.namespace,
1081
1116
  });
1117
+ // Apply template spending limit if not explicitly set
1118
+ if (tpl && tpl.spendingLimit > 0 && record.spendingLimit === 0) {
1119
+ record.spendingLimit = tpl.spendingLimit;
1120
+ this.gate.store.save();
1121
+ }
1122
+ // Apply template auto-topup if not explicitly configured
1123
+ if (tpl?.autoTopup && !record.autoTopup) {
1124
+ record.autoTopup = { ...tpl.autoTopup };
1125
+ this.gate.store.save();
1126
+ }
1082
1127
  // Sync new key to Redis (if configured)
1083
1128
  if (this.redisSync) {
1084
1129
  this.redisSync.saveKey(record).catch(() => { });
@@ -4264,11 +4309,82 @@ class PayGateServer {
4264
4309
  res.writeHead(200, { 'Content-Type': 'application/json' });
4265
4310
  res.end(JSON.stringify({ revoked: true }));
4266
4311
  }
4267
- /**
4268
- * Sync a key mutation to Redis. Call after any local KeyStore mutation
4269
- * (setAcl, setExpiry, setQuota, setTags, setIpAllowlist, setSpendingLimit).
4270
- * Fire-and-forget: errors logged, never thrown.
4271
- */
4312
+ // ─── /keys/templates — CRUD ────────────────────────────────────────────────
4313
+ handleListTemplates(req, res) {
4314
+ if (!this.checkAdmin(req, res))
4315
+ return;
4316
+ const templates = this.templates.list();
4317
+ res.writeHead(200, { 'Content-Type': 'application/json' });
4318
+ res.end(JSON.stringify({ total: templates.length, templates }));
4319
+ }
4320
+ async handleCreateTemplate(req, res) {
4321
+ if (!this.checkAdmin(req, res, 'admin'))
4322
+ return;
4323
+ const body = await this.readBody(req);
4324
+ let params;
4325
+ try {
4326
+ params = JSON.parse(body);
4327
+ }
4328
+ catch {
4329
+ res.writeHead(400, { 'Content-Type': 'application/json' });
4330
+ res.end(JSON.stringify({ error: 'Invalid JSON' }));
4331
+ return;
4332
+ }
4333
+ const name = params.name;
4334
+ if (!name || typeof name !== 'string') {
4335
+ res.writeHead(400, { 'Content-Type': 'application/json' });
4336
+ res.end(JSON.stringify({ error: 'Missing required field: name' }));
4337
+ return;
4338
+ }
4339
+ const existing = this.templates.get(name);
4340
+ const result = this.templates.set(name, params);
4341
+ if (!result.success) {
4342
+ res.writeHead(400, { 'Content-Type': 'application/json' });
4343
+ res.end(JSON.stringify({ error: result.error }));
4344
+ return;
4345
+ }
4346
+ const eventType = existing ? 'template.updated' : 'template.created';
4347
+ this.audit.log(eventType, 'admin', `${existing ? 'Updated' : 'Created'} template: ${name}`, {
4348
+ templateName: name,
4349
+ });
4350
+ res.writeHead(existing ? 200 : 201, { 'Content-Type': 'application/json' });
4351
+ res.end(JSON.stringify({ template: result.template }));
4352
+ }
4353
+ async handleDeleteTemplate(req, res) {
4354
+ if (req.method !== 'POST') {
4355
+ res.writeHead(405, { 'Content-Type': 'application/json' });
4356
+ res.end(JSON.stringify({ error: 'Method not allowed. Use POST.' }));
4357
+ return;
4358
+ }
4359
+ if (!this.checkAdmin(req, res, 'admin'))
4360
+ return;
4361
+ const body = await this.readBody(req);
4362
+ let params;
4363
+ try {
4364
+ params = JSON.parse(body);
4365
+ }
4366
+ catch {
4367
+ res.writeHead(400, { 'Content-Type': 'application/json' });
4368
+ res.end(JSON.stringify({ error: 'Invalid JSON' }));
4369
+ return;
4370
+ }
4371
+ if (!params.name || typeof params.name !== 'string') {
4372
+ res.writeHead(400, { 'Content-Type': 'application/json' });
4373
+ res.end(JSON.stringify({ error: 'Missing required field: name' }));
4374
+ return;
4375
+ }
4376
+ const deleted = this.templates.delete(params.name);
4377
+ if (!deleted) {
4378
+ res.writeHead(404, { 'Content-Type': 'application/json' });
4379
+ res.end(JSON.stringify({ error: `Template "${params.name}" not found` }));
4380
+ return;
4381
+ }
4382
+ this.audit.log('template.deleted', 'admin', `Deleted template: ${params.name}`, {
4383
+ templateName: params.name,
4384
+ });
4385
+ res.writeHead(200, { 'Content-Type': 'application/json' });
4386
+ res.end(JSON.stringify({ deleted: true, name: params.name }));
4387
+ }
4272
4388
  /**
4273
4389
  * Route admin webhook events through the WebhookRouter (for filter rules) or direct emitter.
4274
4390
  */