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.
- package/LICENSE +21 -0
- package/README.md +223 -22
- package/dist/cli.d.ts +1 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +98 -40
- package/dist/cli.js.map +1 -1
- package/dist/dashboard.d.ts +16 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +431 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/gate.d.ts +12 -0
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +46 -3
- package/dist/gate.js.map +1 -1
- package/dist/http-proxy.d.ts +63 -0
- package/dist/http-proxy.d.ts.map +1 -0
- package/dist/http-proxy.js +303 -0
- package/dist/http-proxy.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +14 -3
- package/dist/proxy.js.map +1 -1
- package/dist/server.d.ts +13 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +231 -4
- package/dist/server.js.map +1 -1
- package/dist/store.d.ts +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +5 -0
- package/dist/store.js.map +1 -1
- package/dist/stripe.d.ts +58 -0
- package/dist/stripe.d.ts.map +1 -0
- package/dist/stripe.js +175 -0
- package/dist/stripe.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/dist/webhook.d.ts +21 -0
- package/dist/webhook.d.ts.map +1 -0
- package/dist/webhook.js +89 -0
- package/dist/webhook.js.map +1 -0
- package/package.json +8 -1
|
@@ -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()">↻ 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>🔧 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>⚡ 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>🔑 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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
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
|
}
|
package/dist/gate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAAA
|
|
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:
|
|
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
|
-
|
|
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":";AAAA
|
|
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"}
|