paygate-mcp 0.1.4 → 0.7.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,431 @@
1
+ "use strict";
2
+ /**
3
+ * Admin Dashboard — Embedded HTML dashboard for PayGate MCP.
4
+ *
5
+ * Served at GET /dashboard. Admin key entered via browser prompt.
6
+ * Uses only inline CSS and vanilla JS — no external dependencies.
7
+ * All dynamic content is escaped to prevent XSS.
8
+ *
9
+ * Features:
10
+ * - Overview cards: active keys, total calls, credits spent, denied
11
+ * - Top tools breakdown (bar chart)
12
+ * - Recent activity feed
13
+ * - Key management (create, revoke, top-up)
14
+ * - Auto-refresh every 30s
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.getDashboardHtml = getDashboardHtml;
18
+ function getDashboardHtml(serverName) {
19
+ return `<!DOCTYPE html>
20
+ <html lang="en">
21
+ <head>
22
+ <meta charset="UTF-8">
23
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
24
+ <title>${esc(serverName)} — Dashboard</title>
25
+ <style>
26
+ *{margin:0;padding:0;box-sizing:border-box}
27
+ :root{--bg:#0a0a0f;--surface:#12121a;--border:#1e1e2e;--text:#e0e0e8;--muted:#6b6b80;
28
+ --accent:#6366f1;--accent2:#818cf8;--green:#22c55e;--red:#ef4444;--yellow:#eab308;
29
+ --font:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;--mono:'SF Mono',Monaco,Consolas,monospace}
30
+ body{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh}
31
+ .login{display:flex;align-items:center;justify-content:center;min-height:100vh;flex-direction:column;gap:16px}
32
+ .login h1{font-size:24px;font-weight:600;color:var(--accent2)}
33
+ .login input{background:var(--surface);border:1px solid var(--border);color:var(--text);padding:12px 16px;
34
+ border-radius:8px;font-size:14px;width:320px;font-family:var(--mono)}
35
+ .login input:focus{outline:none;border-color:var(--accent)}
36
+ .login button{background:var(--accent);color:#fff;border:none;padding:12px 24px;border-radius:8px;
37
+ font-size:14px;cursor:pointer;font-weight:500}
38
+ .login button:hover{background:var(--accent2)}
39
+ .login .error{color:var(--red);font-size:13px}
40
+ header{background:var(--surface);border-bottom:1px solid var(--border);padding:16px 24px;
41
+ display:flex;align-items:center;justify-content:space-between}
42
+ header h1{font-size:18px;font-weight:600;display:flex;align-items:center;gap:8px}
43
+ header h1 span{color:var(--accent2)}
44
+ .header-right{display:flex;align-items:center;gap:12px;font-size:13px;color:var(--muted)}
45
+ .refresh-btn{background:var(--surface);border:1px solid var(--border);color:var(--muted);padding:6px 12px;
46
+ border-radius:6px;cursor:pointer;font-size:12px}
47
+ .refresh-btn:hover{border-color:var(--accent);color:var(--text)}
48
+ main{max-width:1200px;margin:0 auto;padding:24px}
49
+ .cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-bottom:24px}
50
+ .card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:20px}
51
+ .card-label{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:8px}
52
+ .card-value{font-size:32px;font-weight:700;font-family:var(--mono)}
53
+ .card-value.green{color:var(--green)}
54
+ .card-value.accent{color:var(--accent2)}
55
+ .card-value.red{color:var(--red)}
56
+ .card-value.yellow{color:var(--yellow)}
57
+ .grid2{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:24px}
58
+ @media(max-width:768px){.grid2{grid-template-columns:1fr}}
59
+ .panel{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:20px}
60
+ .panel h2{font-size:14px;font-weight:600;margin-bottom:16px;display:flex;align-items:center;gap:8px}
61
+ .bar-row{display:flex;align-items:center;gap:8px;margin-bottom:8px}
62
+ .bar-label{font-size:13px;font-family:var(--mono);min-width:140px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
63
+ .bar-track{flex:1;height:24px;background:var(--bg);border-radius:6px;overflow:hidden;position:relative}
64
+ .bar-fill{height:100%;background:linear-gradient(90deg,var(--accent),var(--accent2));border-radius:6px;
65
+ transition:width 0.5s ease;min-width:2px}
66
+ .bar-count{position:absolute;right:8px;top:50%;transform:translateY(-50%);font-size:11px;
67
+ font-family:var(--mono);color:var(--text);z-index:1}
68
+ .feed{max-height:400px;overflow-y:auto}
69
+ .feed-item{display:flex;align-items:center;gap:12px;padding:8px 0;border-bottom:1px solid var(--border);font-size:13px}
70
+ .feed-item:last-child{border-bottom:none}
71
+ .feed-badge{padding:2px 8px;border-radius:4px;font-size:11px;font-family:var(--mono);font-weight:500;white-space:nowrap}
72
+ .feed-badge.allow{background:#22c55e20;color:var(--green)}
73
+ .feed-badge.deny{background:#ef444420;color:var(--red)}
74
+ .feed-tool{font-family:var(--mono);color:var(--accent2);min-width:100px}
75
+ .feed-key{color:var(--muted);font-family:var(--mono);font-size:12px}
76
+ .feed-time{color:var(--muted);font-size:12px;margin-left:auto;white-space:nowrap}
77
+ .keys-section{margin-top:24px}
78
+ .key-actions{display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap}
79
+ .key-actions input{background:var(--bg);border:1px solid var(--border);color:var(--text);padding:8px 12px;
80
+ border-radius:6px;font-size:13px;font-family:var(--mono);width:160px}
81
+ .key-actions input:focus{outline:none;border-color:var(--accent)}
82
+ .btn{border:none;padding:8px 16px;border-radius:6px;font-size:13px;cursor:pointer;font-weight:500}
83
+ .btn-primary{background:var(--accent);color:#fff}
84
+ .btn-primary:hover{background:var(--accent2)}
85
+ .btn-danger{background:var(--red);color:#fff}
86
+ .btn-danger:hover{opacity:0.8}
87
+ .btn-secondary{background:var(--border);color:var(--text)}
88
+ .btn-secondary:hover{background:#2a2a3a}
89
+ .key-list{max-height:300px;overflow-y:auto}
90
+ .key-row{display:flex;align-items:center;gap:12px;padding:10px 0;border-bottom:1px solid var(--border);font-size:13px}
91
+ .key-row:last-child{border-bottom:none}
92
+ .key-prefix{font-family:var(--mono);color:var(--accent2);min-width:100px}
93
+ .key-name{min-width:100px}
94
+ .key-credits{font-family:var(--mono);color:var(--green);min-width:80px}
95
+ .key-status{font-size:11px;padding:2px 8px;border-radius:4px}
96
+ .key-status.active{background:#22c55e20;color:var(--green)}
97
+ .key-status.inactive{background:#ef444420;color:var(--red)}
98
+ .toast{position:fixed;bottom:24px;right:24px;background:var(--surface);border:1px solid var(--accent);
99
+ color:var(--text);padding:12px 20px;border-radius:8px;font-size:13px;z-index:100;display:none;
100
+ box-shadow:0 4px 12px rgba(0,0,0,0.3)}
101
+ #app{display:none}
102
+ </style>
103
+ </head>
104
+ <body>
105
+
106
+ <!-- Login screen -->
107
+ <div class="login" id="login-screen">
108
+ <h1>$ PayGate Dashboard</h1>
109
+ <input type="password" id="admin-key-input" placeholder="Enter admin key..." autofocus>
110
+ <button onclick="doLogin()">Sign In</button>
111
+ <div class="error" id="login-error"></div>
112
+ </div>
113
+
114
+ <!-- Dashboard -->
115
+ <div id="app">
116
+ <header>
117
+ <h1><span>$</span> ${esc(serverName)}</h1>
118
+ <div class="header-right">
119
+ <span id="last-refresh"></span>
120
+ <button class="refresh-btn" onclick="refresh()">&#x21bb; Refresh</button>
121
+ </div>
122
+ </header>
123
+
124
+ <main>
125
+ <!-- Overview cards -->
126
+ <div class="cards">
127
+ <div class="card">
128
+ <div class="card-label">Active Keys</div>
129
+ <div class="card-value accent" id="stat-keys">0</div>
130
+ </div>
131
+ <div class="card">
132
+ <div class="card-label">Total Calls</div>
133
+ <div class="card-value green" id="stat-calls">0</div>
134
+ </div>
135
+ <div class="card">
136
+ <div class="card-label">Credits Spent</div>
137
+ <div class="card-value yellow" id="stat-credits">0</div>
138
+ </div>
139
+ <div class="card">
140
+ <div class="card-label">Denied</div>
141
+ <div class="card-value red" id="stat-denied">0</div>
142
+ </div>
143
+ </div>
144
+
145
+ <!-- Tools + Activity -->
146
+ <div class="grid2">
147
+ <div class="panel">
148
+ <h2>&#x1f527; Top Tools</h2>
149
+ <div id="tools-chart"></div>
150
+ <div id="tools-empty" style="color:var(--muted);font-size:13px">No tool calls yet.</div>
151
+ </div>
152
+ <div class="panel">
153
+ <h2>&#x26a1; Recent Activity</h2>
154
+ <div class="feed" id="activity-feed"></div>
155
+ <div id="feed-empty" style="color:var(--muted);font-size:13px">No activity yet.</div>
156
+ </div>
157
+ </div>
158
+
159
+ <!-- Keys Management -->
160
+ <div class="panel keys-section">
161
+ <h2>&#x1f511; API Keys</h2>
162
+ <div class="key-actions">
163
+ <input type="text" id="new-key-name" placeholder="Key name...">
164
+ <input type="number" id="new-key-credits" placeholder="Credits" value="100" min="1">
165
+ <button class="btn btn-primary" onclick="createKey()">Create Key</button>
166
+ </div>
167
+ <div class="key-list" id="key-list"></div>
168
+ </div>
169
+ </main>
170
+ </div>
171
+
172
+ <div class="toast" id="toast"></div>
173
+
174
+ <script>
175
+ // All dynamic content is escaped via esc() to prevent XSS.
176
+ // The dashboard only talks to the same-origin PayGate server.
177
+ let ADMIN_KEY = '';
178
+ const BASE = window.location.origin;
179
+
180
+ async function api(path, opts) {
181
+ opts = opts || {};
182
+ const res = await fetch(BASE + path, {
183
+ method: opts.method || 'GET',
184
+ headers: {
185
+ 'Content-Type': 'application/json',
186
+ 'X-Admin-Key': ADMIN_KEY,
187
+ },
188
+ body: opts.body || undefined,
189
+ });
190
+ const text = await res.text();
191
+ try { return { status: res.status, body: JSON.parse(text) }; }
192
+ catch(e) { return { status: res.status, body: text }; }
193
+ }
194
+
195
+ function doLogin() {
196
+ ADMIN_KEY = document.getElementById('admin-key-input').value.trim();
197
+ if (!ADMIN_KEY) return;
198
+ api('/status').then(function(r) {
199
+ if (r.status === 200) {
200
+ document.getElementById('login-screen').style.display = 'none';
201
+ document.getElementById('app').style.display = 'block';
202
+ refresh();
203
+ setInterval(refresh, 30000);
204
+ } else {
205
+ document.getElementById('login-error').textContent = 'Invalid admin key.';
206
+ }
207
+ }).catch(function() {
208
+ document.getElementById('login-error').textContent = 'Connection failed.';
209
+ });
210
+ }
211
+
212
+ document.getElementById('admin-key-input').addEventListener('keydown', function(e) {
213
+ if (e.key === 'Enter') doLogin();
214
+ });
215
+
216
+ // Escape HTML to prevent XSS - all user/server data goes through this
217
+ function esc(s) {
218
+ if (!s) return '';
219
+ var d = document.createElement('div');
220
+ d.appendChild(document.createTextNode(String(s)));
221
+ return d.textContent ? d.textContent : '';
222
+ }
223
+
224
+ // Safe text setter - uses textContent, not innerHTML
225
+ function setText(id, text) {
226
+ var el = document.getElementById(id);
227
+ if (el) el.textContent = String(text);
228
+ }
229
+
230
+ // Build tool chart using safe DOM methods
231
+ function buildToolsChart(perTool) {
232
+ var container = document.getElementById('tools-chart');
233
+ var emptyEl = document.getElementById('tools-empty');
234
+ container.textContent = ''; // clear safely
235
+
236
+ var entries = Object.entries(perTool).sort(function(a, b) { return b[1].calls - a[1].calls; }).slice(0, 10);
237
+ if (entries.length === 0) {
238
+ emptyEl.style.display = 'block';
239
+ return;
240
+ }
241
+ emptyEl.style.display = 'none';
242
+ var maxCalls = entries[0][1].calls;
243
+
244
+ entries.forEach(function(entry) {
245
+ var name = entry[0];
246
+ var data = entry[1];
247
+ var pct = Math.max(2, (data.calls / maxCalls) * 100);
248
+
249
+ var row = document.createElement('div');
250
+ row.className = 'bar-row';
251
+
252
+ var label = document.createElement('div');
253
+ label.className = 'bar-label';
254
+ label.textContent = name;
255
+
256
+ var track = document.createElement('div');
257
+ track.className = 'bar-track';
258
+
259
+ var fill = document.createElement('div');
260
+ fill.className = 'bar-fill';
261
+ fill.style.width = pct + '%';
262
+
263
+ var count = document.createElement('div');
264
+ count.className = 'bar-count';
265
+ count.textContent = data.calls + ' calls / ' + data.credits + ' cr';
266
+
267
+ track.appendChild(fill);
268
+ track.appendChild(count);
269
+ row.appendChild(label);
270
+ row.appendChild(track);
271
+ container.appendChild(row);
272
+ });
273
+ }
274
+
275
+ // Build activity feed using safe DOM methods
276
+ function buildActivityFeed(events) {
277
+ var feedEl = document.getElementById('activity-feed');
278
+ var emptyEl = document.getElementById('feed-empty');
279
+ feedEl.textContent = ''; // clear safely
280
+
281
+ var items = (events || []).slice(-50).reverse();
282
+ if (items.length === 0) {
283
+ emptyEl.style.display = 'block';
284
+ return;
285
+ }
286
+ emptyEl.style.display = 'none';
287
+
288
+ items.forEach(function(e) {
289
+ var item = document.createElement('div');
290
+ item.className = 'feed-item';
291
+
292
+ var badge = document.createElement('span');
293
+ badge.className = 'feed-badge ' + (e.allowed ? 'allow' : 'deny');
294
+ badge.textContent = e.allowed ? 'OK' : 'DENY';
295
+
296
+ var tool = document.createElement('span');
297
+ tool.className = 'feed-tool';
298
+ tool.textContent = e.tool;
299
+
300
+ var key = document.createElement('span');
301
+ key.className = 'feed-key';
302
+ key.textContent = e.apiKey;
303
+
304
+ item.appendChild(badge);
305
+ item.appendChild(tool);
306
+ item.appendChild(key);
307
+
308
+ if (e.creditsCharged > 0) {
309
+ var cr = document.createElement('span');
310
+ cr.style.cssText = 'color:var(--yellow);font-family:var(--mono);font-size:12px';
311
+ cr.textContent = '-' + e.creditsCharged;
312
+ item.appendChild(cr);
313
+ }
314
+
315
+ var time = document.createElement('span');
316
+ time.className = 'feed-time';
317
+ time.textContent = new Date(e.timestamp).toLocaleTimeString();
318
+ item.appendChild(time);
319
+
320
+ feedEl.appendChild(item);
321
+ });
322
+ }
323
+
324
+ // Build keys list using safe DOM methods
325
+ function buildKeysList(keys) {
326
+ var container = document.getElementById('key-list');
327
+ container.textContent = ''; // clear safely
328
+
329
+ (keys || []).forEach(function(k) {
330
+ var row = document.createElement('div');
331
+ row.className = 'key-row';
332
+
333
+ var prefix = document.createElement('span');
334
+ prefix.className = 'key-prefix';
335
+ prefix.textContent = k.keyPrefix;
336
+
337
+ var name = document.createElement('span');
338
+ name.className = 'key-name';
339
+ name.textContent = k.name;
340
+
341
+ var credits = document.createElement('span');
342
+ credits.className = 'key-credits';
343
+ credits.textContent = k.credits + ' cr';
344
+
345
+ var status = document.createElement('span');
346
+ status.className = 'key-status ' + (k.active ? 'active' : 'inactive');
347
+ status.textContent = k.active ? 'Active' : 'Revoked';
348
+
349
+ var calls = document.createElement('span');
350
+ calls.style.cssText = 'color:var(--muted);font-size:12px';
351
+ calls.textContent = k.totalCalls + ' calls';
352
+
353
+ row.appendChild(prefix);
354
+ row.appendChild(name);
355
+ row.appendChild(credits);
356
+ row.appendChild(status);
357
+ row.appendChild(calls);
358
+ container.appendChild(row);
359
+ });
360
+ }
361
+
362
+ async function refresh() {
363
+ try {
364
+ var results = await Promise.all([
365
+ api('/status'),
366
+ api('/usage'),
367
+ api('/keys'),
368
+ ]);
369
+ var statusRes = results[0];
370
+ var usageRes = results[1];
371
+ var keysRes = results[2];
372
+
373
+ if (statusRes.status !== 200) return;
374
+ var s = statusRes.body;
375
+
376
+ // Overview cards - using textContent (safe)
377
+ setText('stat-keys', s.activeKeys || 0);
378
+ setText('stat-calls', (s.usage && s.usage.totalCalls || 0).toLocaleString());
379
+ setText('stat-credits', (s.usage && s.usage.totalCreditsSpent || 0).toLocaleString());
380
+ setText('stat-denied', (s.usage && s.usage.totalDenied || 0).toLocaleString());
381
+
382
+ // Tools chart - using safe DOM methods
383
+ buildToolsChart(s.usage && s.usage.perTool || {});
384
+
385
+ // Activity feed - using safe DOM methods
386
+ if (usageRes.status === 200 && usageRes.body.events) {
387
+ buildActivityFeed(usageRes.body.events);
388
+ }
389
+
390
+ // Keys list - using safe DOM methods
391
+ if (keysRes.status === 200 && Array.isArray(keysRes.body)) {
392
+ buildKeysList(keysRes.body);
393
+ }
394
+
395
+ setText('last-refresh', 'Updated ' + new Date().toLocaleTimeString());
396
+ } catch (err) {
397
+ console.error('Refresh failed:', err);
398
+ }
399
+ }
400
+
401
+ async function createKey() {
402
+ var name = document.getElementById('new-key-name').value.trim() || 'unnamed';
403
+ var credits = parseInt(document.getElementById('new-key-credits').value) || 100;
404
+ var res = await api('/keys', {
405
+ method: 'POST',
406
+ body: JSON.stringify({ name: name, credits: credits }),
407
+ });
408
+ if (res.status === 201) {
409
+ toast('Key created: ' + res.body.key);
410
+ document.getElementById('new-key-name').value = '';
411
+ refresh();
412
+ } else {
413
+ toast('Error: ' + (res.body.error || 'Failed'), true);
414
+ }
415
+ }
416
+
417
+ function toast(msg, isError) {
418
+ var el = document.getElementById('toast');
419
+ el.textContent = msg; // safe: textContent
420
+ el.style.borderColor = isError ? 'var(--red)' : 'var(--accent)';
421
+ el.style.display = 'block';
422
+ setTimeout(function() { el.style.display = 'none'; }, 5000);
423
+ }
424
+ </script>
425
+ </body>
426
+ </html>`;
427
+ }
428
+ function esc(s) {
429
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
430
+ }
431
+ //# sourceMappingURL=dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;AAEH,4CAyZC;AAzZD,SAAgB,gBAAgB,CAAC,UAAkB;IACjD,OAAO;;;;;SAKA,GAAG,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA6FC,GAAG,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAqThC,CAAC;AACT,CAAC;AAED,SAAS,GAAG,CAAC,CAAS;IACpB,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACtG,CAAC"}
package/dist/gate.d.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  * 1. API key validity
6
6
  * 2. Credit balance
7
7
  * 3. Rate limit
8
+ * 4. Spending limit
8
9
  *
9
10
  * Fail-closed: any check failure => DENY.
10
11
  * Shadow mode: log but don't enforce (always ALLOW).
@@ -13,10 +14,12 @@ import { PayGateConfig, GateDecision, ToolCallParams } from './types';
13
14
  import { KeyStore } from './store';
14
15
  import { RateLimiter } from './rate-limiter';
15
16
  import { UsageMeter } from './meter';
17
+ import { WebhookEmitter } from './webhook';
16
18
  export declare class Gate {
17
19
  readonly store: KeyStore;
18
20
  readonly rateLimiter: RateLimiter;
19
21
  readonly meter: UsageMeter;
22
+ readonly webhook: WebhookEmitter | null;
20
23
  private readonly config;
21
24
  constructor(config: PayGateConfig, statePath?: string);
22
25
  /**
@@ -47,8 +50,17 @@ export declare class Gate {
47
50
  defaultCreditsPerCall: number;
48
51
  globalRateLimitPerMin: number;
49
52
  toolPricing: Record<string, import("./types").ToolPricing>;
53
+ refundOnFailure: boolean;
54
+ webhookUrl: string | null;
50
55
  };
51
56
  };
57
+ /**
58
+ * Refund credits for a failed tool call.
59
+ * Only used when refundOnFailure is enabled.
60
+ */
61
+ refund(apiKey: string, toolName: string, credits: number): void;
62
+ /** Whether refund-on-failure is enabled */
63
+ get refundOnFailure(): boolean;
52
64
  destroy(): void;
53
65
  private recordEvent;
54
66
  }
@@ -1 +1 @@
1
- {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,aAAa,EAAE,YAAY,EAAc,cAAc,EAAE,MAAM,SAAS,CAAC;AAClF,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,qBAAa,IAAI;IACf,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;gBAE3B,MAAM,EAAE,aAAa,EAAE,SAAS,CAAC,EAAE,MAAM;IAOrD;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,cAAc,GAAG,YAAY;IAwDvE;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIrC;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAMtC;;OAEG;IACH,SAAS;;;;;;;;;;;;;;;IAgBT,OAAO,IAAI,IAAI;IAIf,OAAO,CAAC,WAAW;CAcpB"}
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,aAAa,EAAE,YAAY,EAAc,cAAc,EAAE,MAAM,SAAS,CAAC;AAClF,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,qBAAa,IAAI;IACf,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;gBAE3B,MAAM,EAAE,aAAa,EAAE,SAAS,CAAC,EAAE,MAAM;IAQrD;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,cAAc,GAAG,YAAY;IAyEvE;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIrC;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAMtC;;OAEG;IACH,SAAS;;;;;;;;;;;;;;;;;IAkBT;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAW/D,2CAA2C;IAC3C,IAAI,eAAe,IAAI,OAAO,CAE7B;IAED,OAAO,IAAI,IAAI;IAKf,OAAO,CAAC,WAAW;CAgBpB"}
package/dist/gate.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * 1. API key validity
7
7
  * 2. Credit balance
8
8
  * 3. Rate limit
9
+ * 4. Spending limit
9
10
  *
10
11
  * Fail-closed: any check failure => DENY.
11
12
  * Shadow mode: log but don't enforce (always ALLOW).
@@ -15,16 +16,19 @@ exports.Gate = void 0;
15
16
  const store_1 = require("./store");
16
17
  const rate_limiter_1 = require("./rate-limiter");
17
18
  const meter_1 = require("./meter");
19
+ const webhook_1 = require("./webhook");
18
20
  class Gate {
19
21
  store;
20
22
  rateLimiter;
21
23
  meter;
24
+ webhook;
22
25
  config;
23
26
  constructor(config, statePath) {
24
27
  this.config = config;
25
28
  this.store = new store_1.KeyStore(statePath);
26
29
  this.rateLimiter = new rate_limiter_1.RateLimiter(config.globalRateLimitPerMin);
27
30
  this.meter = new meter_1.UsageMeter();
31
+ this.webhook = config.webhookUrl ? new webhook_1.WebhookEmitter(config.webhookUrl) : null;
28
32
  }
29
33
  /**
30
34
  * Evaluate a tool call request.
@@ -71,7 +75,23 @@ class Gate {
71
75
  remainingCredits: keyRecord.credits,
72
76
  };
73
77
  }
74
- // Step 5: ALLOW — deduct credits and record
78
+ // Step 5: Spending limit?
79
+ if (keyRecord.spendingLimit > 0) {
80
+ const wouldSpend = keyRecord.totalSpent + creditsRequired;
81
+ if (wouldSpend > keyRecord.spendingLimit) {
82
+ this.recordEvent(apiKey, keyRecord.name, toolName, 0, false, 'spending_limit_exceeded');
83
+ if (this.config.shadowMode) {
84
+ return { allowed: true, reason: 'shadow:spending_limit_exceeded', creditsCharged: 0, remainingCredits: keyRecord.credits };
85
+ }
86
+ return {
87
+ allowed: false,
88
+ reason: `spending_limit_exceeded: limit ${keyRecord.spendingLimit}, spent ${keyRecord.totalSpent}, need ${creditsRequired}`,
89
+ creditsCharged: 0,
90
+ remainingCredits: keyRecord.credits,
91
+ };
92
+ }
93
+ }
94
+ // Step 6: ALLOW — deduct credits and record
75
95
  this.store.deductCredits(apiKey, creditsRequired);
76
96
  this.rateLimiter.record(apiKey);
77
97
  const remaining = this.store.getKey(apiKey)?.credits ?? 0;
@@ -108,14 +128,35 @@ class Gate {
108
128
  defaultCreditsPerCall: this.config.defaultCreditsPerCall,
109
129
  globalRateLimitPerMin: this.config.globalRateLimitPerMin,
110
130
  toolPricing: this.config.toolPricing,
131
+ refundOnFailure: this.config.refundOnFailure,
132
+ webhookUrl: this.config.webhookUrl ? '***' : null,
111
133
  },
112
134
  };
113
135
  }
136
+ /**
137
+ * Refund credits for a failed tool call.
138
+ * Only used when refundOnFailure is enabled.
139
+ */
140
+ refund(apiKey, toolName, credits) {
141
+ this.store.addCredits(apiKey, credits);
142
+ const keyRecord = this.store.getKey(apiKey);
143
+ if (keyRecord) {
144
+ keyRecord.totalSpent = Math.max(0, keyRecord.totalSpent - credits);
145
+ keyRecord.totalCalls = Math.max(0, keyRecord.totalCalls - 1);
146
+ this.store.save();
147
+ }
148
+ this.recordEvent(apiKey, keyRecord?.name || 'unknown', toolName, -credits, true, 'refund');
149
+ }
150
+ /** Whether refund-on-failure is enabled */
151
+ get refundOnFailure() {
152
+ return this.config.refundOnFailure;
153
+ }
114
154
  destroy() {
115
155
  this.rateLimiter.destroy();
156
+ this.webhook?.destroy();
116
157
  }
117
158
  recordEvent(apiKey, keyName, tool, creditsCharged, allowed, denyReason) {
118
- this.meter.record({
159
+ const event = {
119
160
  timestamp: new Date().toISOString(),
120
161
  apiKey: apiKey.slice(0, 10),
121
162
  keyName,
@@ -123,7 +164,9 @@ class Gate {
123
164
  creditsCharged,
124
165
  allowed,
125
166
  denyReason,
126
- });
167
+ };
168
+ this.meter.record(event);
169
+ this.webhook?.emit(event);
127
170
  }
128
171
  }
129
172
  exports.Gate = Gate;
package/dist/gate.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"gate.js","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * HttpMcpProxy — Forwards JSON-RPC requests to a remote MCP server
3
+ * using the Streamable HTTP transport (MCP spec 2025-03-26).
4
+ *
5
+ * Architecture:
6
+ * HTTP Client <--JSON-RPC over HTTP--> PayGate <--Streamable HTTP--> Remote MCP Server
7
+ *
8
+ * Supports:
9
+ * - POST with application/json responses
10
+ * - POST with text/event-stream (SSE) responses
11
+ * - Mcp-Session-Id session management
12
+ * - Automatic initialization handshake
13
+ */
14
+ import { EventEmitter } from 'events';
15
+ import { JsonRpcRequest, JsonRpcResponse } from './types';
16
+ import { Gate } from './gate';
17
+ export declare class HttpMcpProxy extends EventEmitter {
18
+ private readonly gate;
19
+ private readonly remoteUrl;
20
+ private sessionId;
21
+ private started;
22
+ constructor(gate: Gate, remoteUrl: string);
23
+ /**
24
+ * Start the proxy — verify the remote server is reachable.
25
+ */
26
+ start(): Promise<void>;
27
+ /**
28
+ * Handle an incoming JSON-RPC request from the client.
29
+ * Gates tools/call through the PayGate. Passes other methods through.
30
+ */
31
+ handleRequest(request: JsonRpcRequest, apiKey: string | null): Promise<JsonRpcResponse>;
32
+ /**
33
+ * Forward a JSON-RPC request to the remote MCP server via HTTP POST.
34
+ * Handles both application/json and text/event-stream responses.
35
+ */
36
+ private forwardToServer;
37
+ /**
38
+ * Send an HTTP POST to the remote MCP server.
39
+ */
40
+ private httpPost;
41
+ /**
42
+ * Parse an SSE (text/event-stream) response body to extract JSON-RPC responses.
43
+ *
44
+ * SSE format:
45
+ * event: message
46
+ * data: {"jsonrpc":"2.0","id":1,"result":{...}}
47
+ *
48
+ * Per the MCP spec, the stream may contain multiple events.
49
+ * We look for the JSON-RPC response matching our request ID.
50
+ */
51
+ private parseSseResponse;
52
+ /**
53
+ * Stop the proxy and clean up the session.
54
+ */
55
+ stop(): Promise<void>;
56
+ /**
57
+ * Send HTTP DELETE to terminate the MCP session.
58
+ */
59
+ private httpDelete;
60
+ get isRunning(): boolean;
61
+ private errorResponse;
62
+ }
63
+ //# sourceMappingURL=http-proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-proxy.d.ts","sourceRoot":"","sources":["../src/http-proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAkB,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAI9B,qBAAa,YAAa,SAAQ,YAAY;IAC5C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAO;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAM;IAChC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,OAAO,CAAS;gBAEZ,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM;IAMzC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B;;;OAGG;IACG,aAAa,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC;IAoD7F;;;OAGG;YACW,eAAe;IAoC7B;;OAEG;IACH,OAAO,CAAC,QAAQ;IAkDhB;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;IA0CxB;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAe3B;;OAEG;IACH,OAAO,CAAC,UAAU;IA0BlB,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,OAAO,CAAC,aAAa;CAGtB"}