paygate-mcp 8.4.0 → 8.6.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/README.md CHANGED
@@ -111,6 +111,8 @@ Agent → PayGate (auth + billing) → Your MCP Server (stdio or HTTP)
111
111
  - **System Dashboard** — `GET /admin/dashboard` system-wide overview with key counts (active/suspended/revoked/expired), credit summary (allocated/spent/remaining), usage breakdown with deny reasons, top consumers, top tools, notification counts, and uptime
112
112
  - **Key Lifecycle Report** — `GET /admin/lifecycle` aggregated lifecycle trends with daily creation/revocation/suspension buckets, average key lifetime, and at-risk keys (expiring, expired, zero credits)
113
113
  - **Cost Analysis** — `GET /admin/costs` cost-centric view with per-tool and per-namespace cost breakdowns, hourly spending trends, top spenders, average cost per call, and namespace filtering
114
+ - **Rate Limit Analysis** — `GET /admin/rate-limits` rate limit utilization analysis with per-key and per-tool breakdown, denial trends, most throttled keys, and current window utilization
115
+ - **Quota Analysis** — `GET /admin/quotas` quota utilization analysis with per-key daily/monthly usage vs limits, per-tool denial breakdown, most constrained keys, and global/per-key quota source tracking
114
116
  - **Config Hot Reload** — `POST /config/reload` reloads pricing, rate limits, webhooks, quotas, and behavior flags from config file without server restart
115
117
  - **Webhook Events** — POST batched usage events to any URL for external billing/alerting
116
118
  - **Config File Mode** — Load all settings from a JSON file (`--config`)
@@ -2551,6 +2553,67 @@ curl "http://localhost:3402/admin/costs?since=2026-02-01" \
2551
2553
 
2552
2554
  Returns per-tool cost breakdown (with average cost per call), per-namespace spending, hourly trend buckets (last 24 hours), and top 10 spenders ranked by credits consumed. Supports `?since=` and `?namespace=` query filters. Keys without an explicit namespace appear under `default`. Read-only.
2553
2555
 
2556
+ ### Rate Limit Analysis
2557
+
2558
+ Analyze rate limit utilization across keys and tools:
2559
+
2560
+ ```bash
2561
+ curl http://localhost:3402/admin/rate-limits \
2562
+ -H "X-Admin-Key: YOUR_ADMIN_KEY"
2563
+ ```
2564
+
2565
+ ```json
2566
+ {
2567
+ "config": {
2568
+ "globalLimitPerMin": 60,
2569
+ "windowMs": 60000
2570
+ },
2571
+ "summary": {
2572
+ "totalCalls": 450,
2573
+ "totalRateLimited": 12,
2574
+ "rateLimitRate": 0.0267
2575
+ },
2576
+ "perKey": [
2577
+ { "name": "ml-pipeline", "calls": 200, "rateLimited": 8, "currentWindowUsed": 45, "currentWindowRemaining": 15 },
2578
+ { "name": "batch-worker", "calls": 150, "rateLimited": 4, "currentWindowUsed": 12, "currentWindowRemaining": 48 }
2579
+ ],
2580
+ "perTool": [
2581
+ { "tool": "generate_report", "calls": 180, "rateLimited": 10 },
2582
+ { "tool": "query_data", "calls": 270, "rateLimited": 2 }
2583
+ ],
2584
+ "hourlyTrends": [
2585
+ { "hour": "2026-02-26T14", "calls": 52, "rateLimited": 3 },
2586
+ { "hour": "2026-02-26T15", "calls": 48, "rateLimited": 1 }
2587
+ ],
2588
+ "mostThrottled": [
2589
+ { "name": "ml-pipeline", "rateLimited": 8, "calls": 200, "throttleRate": 0.04 }
2590
+ ]
2591
+ }
2592
+ ```
2593
+
2594
+ Returns rate limit configuration, denial summary with throttle rate, per-key breakdown with current sliding window utilization, per-tool denial counts, hourly denial trends (last 24 hours), and top 10 most throttled keys ranked by denial count. Handles unlimited rate limits (globalLimitPerMin: 0). Read-only.
2595
+
2596
+ ### Quota Analysis
2597
+
2598
+ ```bash
2599
+ curl http://localhost:3000/admin/quotas -H "X-Admin-Key: YOUR_ADMIN_KEY"
2600
+ ```
2601
+
2602
+ ```json
2603
+ {
2604
+ "config": { "globalQuota": { "dailyCallLimit": 100, "monthlyCallLimit": 1000, "dailyCreditLimit": 500, "monthlyCreditLimit": 5000 } },
2605
+ "summary": { "totalKeys": 5, "keysWithQuotas": 4, "totalQuotaDenials": 3, "quotaDenialRate": 0.02 },
2606
+ "perKey": [
2607
+ { "name": "heavy-user", "dailyCalls": 95, "monthlyCalls": 450, "dailyCredits": 475, "monthlyCredits": 2250, "dailyCallLimit": 100, "monthlyCallLimit": 1000, "dailyCreditLimit": 500, "monthlyCreditLimit": 5000, "dailyCallUtilization": 0.95, "monthlyCallUtilization": 0.45, "source": "global" }
2608
+ ],
2609
+ "perTool": [{ "tool": "summarize", "calls": 120, "quotaDenied": 2 }],
2610
+ "hourlyTrends": [{ "hour": "2025-01-15T14", "calls": 15, "quotaDenied": 1 }],
2611
+ "mostConstrained": [{ "name": "heavy-user", "dailyCalls": 95, "dailyCallLimit": 100, "dailyCallUtilization": 0.95, "monthlyCalls": 450, "monthlyCallLimit": 1000, "monthlyCallUtilization": 0.45 }]
2612
+ }
2613
+ ```
2614
+
2615
+ Returns quota configuration (global or null), key counts with/without quotas, denial summary with denial rate, per-key daily/monthly call and credit usage vs limits with utilization percentages, quota source (per-key/global/none), per-tool quota denial counts, hourly denial trends (last 24 hours), and top 10 most constrained keys ranked by daily call utilization. Read-only.
2616
+
2554
2617
  ### IP Allowlisting
2555
2618
 
2556
2619
  Restrict API keys to specific IP addresses or CIDR ranges:
package/dist/server.d.ts CHANGED
@@ -246,6 +246,8 @@ export declare class PayGateServer {
246
246
  private handleSystemDashboard;
247
247
  private handleKeyLifecycleReport;
248
248
  private handleCostAnalysis;
249
+ private handleRateLimitAnalysis;
250
+ private handleQuotaAnalysis;
249
251
  private handleGetNotes;
250
252
  private handleAddNote;
251
253
  private handleDeleteNote;
@@ -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
@@ -822,6 +822,18 @@ class PayGateServer {
822
822
  res.writeHead(405, { 'Content-Type': 'application/json' });
823
823
  res.end(JSON.stringify({ error: 'Method not allowed. Use GET.' }));
824
824
  return;
825
+ case '/admin/rate-limits':
826
+ if (req.method === 'GET')
827
+ return this.handleRateLimitAnalysis(req, res);
828
+ res.writeHead(405, { 'Content-Type': 'application/json' });
829
+ res.end(JSON.stringify({ error: 'Method not allowed. Use GET.' }));
830
+ return;
831
+ case '/admin/quotas':
832
+ if (req.method === 'GET')
833
+ return this.handleQuotaAnalysis(req, res);
834
+ res.writeHead(405, { 'Content-Type': 'application/json' });
835
+ res.end(JSON.stringify({ error: 'Method not allowed. Use GET.' }));
836
+ return;
825
837
  // ─── Plugin endpoints ──────────────────────────────────────────────
826
838
  case '/plugins':
827
839
  return this.handleListPlugins(req, res);
@@ -1366,6 +1378,8 @@ class PayGateServer {
1366
1378
  systemDashboard: 'GET /admin/dashboard — System-wide overview with key stats, credit summary, usage breakdown, top consumers, and uptime (requires X-Admin-Key)',
1367
1379
  keyLifecycle: 'GET /admin/lifecycle — Key lifecycle report with creation/revocation/expiry trends, average lifetime, and at-risk keys (requires X-Admin-Key)',
1368
1380
  costAnalysis: 'GET /admin/costs — Cost analysis with per-tool, per-namespace breakdown, hourly trends, and top spenders (requires X-Admin-Key)',
1381
+ rateLimitAnalysis: 'GET /admin/rate-limits — Rate limit utilization analysis with per-key and per-tool breakdown, denial trends, and most throttled keys (requires X-Admin-Key)',
1382
+ quotaAnalysis: 'GET /admin/quotas — Quota utilization analysis with per-key and per-tool breakdown, denial trends, most constrained keys, and configuration display (requires X-Admin-Key)',
1369
1383
  ...(this.oauth ? {
1370
1384
  oauthMetadata: 'GET /.well-known/oauth-authorization-server — OAuth 2.1 server metadata',
1371
1385
  oauthRegister: 'POST /oauth/register — Register OAuth client',
@@ -4783,6 +4797,221 @@ class PayGateServer {
4783
4797
  topSpenders,
4784
4798
  }));
4785
4799
  }
4800
+ // ─── /admin/rate-limits — Rate limit utilization analysis ───────────────
4801
+ handleRateLimitAnalysis(req, res) {
4802
+ if (!this.checkAdmin(req, res))
4803
+ return;
4804
+ const rateLimiter = this.gate.rateLimiter;
4805
+ const globalLimit = rateLimiter.globalLimit;
4806
+ // Get all usage events to analyze rate limit denials
4807
+ const events = this.gate.meter.getEvents();
4808
+ // ── Summary ──
4809
+ let totalCalls = 0;
4810
+ let totalRateLimited = 0;
4811
+ for (const e of events) {
4812
+ totalCalls++;
4813
+ if (!e.allowed && e.denyReason?.includes('rate_limited')) {
4814
+ totalRateLimited++;
4815
+ }
4816
+ }
4817
+ const rateLimitRate = totalCalls > 0
4818
+ ? Math.round(totalRateLimited / totalCalls * 10000) / 10000
4819
+ : 0;
4820
+ // ── Per-key breakdown ──
4821
+ // Build a name→fullKey map from the key store for rate limiter status lookups
4822
+ const allRecords = this.gate.store.getAllRecords();
4823
+ const nameToFullKey = new Map();
4824
+ for (const rec of allRecords) {
4825
+ nameToFullKey.set(rec.name, rec.key);
4826
+ }
4827
+ const keyMap = new Map();
4828
+ for (const e of events) {
4829
+ const name = e.keyName || e.apiKey.slice(0, 10);
4830
+ if (!keyMap.has(name))
4831
+ keyMap.set(name, { name, calls: 0, rateLimited: 0 });
4832
+ const k = keyMap.get(name);
4833
+ k.calls++;
4834
+ if (!e.allowed && e.denyReason?.includes('rate_limited')) {
4835
+ k.rateLimited++;
4836
+ }
4837
+ }
4838
+ // Add current window utilization using full key from store
4839
+ const perKey = Array.from(keyMap.values()).map(k => {
4840
+ const fullKey = nameToFullKey.get(k.name) || '';
4841
+ const status = fullKey ? rateLimiter.getStatus(fullKey) : { used: 0, remaining: globalLimit > 0 ? globalLimit : Infinity };
4842
+ return {
4843
+ name: k.name,
4844
+ calls: k.calls,
4845
+ rateLimited: k.rateLimited,
4846
+ currentWindowUsed: status.used,
4847
+ currentWindowRemaining: status.remaining,
4848
+ };
4849
+ }).sort((a, b) => b.calls - a.calls);
4850
+ // ── Per-tool breakdown ──
4851
+ const toolMap = new Map();
4852
+ for (const e of events) {
4853
+ if (!toolMap.has(e.tool))
4854
+ toolMap.set(e.tool, { calls: 0, rateLimited: 0 });
4855
+ const t = toolMap.get(e.tool);
4856
+ t.calls++;
4857
+ if (!e.allowed && e.denyReason?.includes('rate_limited')) {
4858
+ t.rateLimited++;
4859
+ }
4860
+ }
4861
+ const perTool = Array.from(toolMap.entries())
4862
+ .map(([tool, stats]) => ({ tool, ...stats }))
4863
+ .sort((a, b) => b.rateLimited - a.rateLimited);
4864
+ // ── Hourly trends (rate limit denials) ──
4865
+ const hourBuckets = new Map();
4866
+ for (const e of events) {
4867
+ const hour = e.timestamp.slice(0, 13); // YYYY-MM-DDTHH
4868
+ if (!hourBuckets.has(hour))
4869
+ hourBuckets.set(hour, { calls: 0, rateLimited: 0 });
4870
+ const h = hourBuckets.get(hour);
4871
+ h.calls++;
4872
+ if (!e.allowed && e.denyReason?.includes('rate_limited')) {
4873
+ h.rateLimited++;
4874
+ }
4875
+ }
4876
+ const hourlyTrends = Array.from(hourBuckets.entries())
4877
+ .map(([hour, stats]) => ({ hour, ...stats }))
4878
+ .sort((a, b) => a.hour.localeCompare(b.hour))
4879
+ .slice(-24);
4880
+ // ── Most throttled keys (by rate limit denials) ──
4881
+ const mostThrottled = Array.from(keyMap.values())
4882
+ .filter(k => k.rateLimited > 0)
4883
+ .map(k => ({
4884
+ name: k.name,
4885
+ rateLimited: k.rateLimited,
4886
+ calls: k.calls,
4887
+ throttleRate: Math.round(k.rateLimited / k.calls * 10000) / 10000,
4888
+ }))
4889
+ .sort((a, b) => b.rateLimited - a.rateLimited)
4890
+ .slice(0, 10);
4891
+ res.writeHead(200, { 'Content-Type': 'application/json' });
4892
+ res.end(JSON.stringify({
4893
+ config: {
4894
+ globalLimitPerMin: globalLimit,
4895
+ windowMs: 60000,
4896
+ },
4897
+ summary: {
4898
+ totalCalls,
4899
+ totalRateLimited,
4900
+ rateLimitRate,
4901
+ },
4902
+ perKey,
4903
+ perTool,
4904
+ hourlyTrends,
4905
+ mostThrottled,
4906
+ }));
4907
+ }
4908
+ // ─── /admin/quotas — Quota utilization analysis ───────────────────────────
4909
+ handleQuotaAnalysis(req, res) {
4910
+ if (!this.checkAdmin(req, res))
4911
+ return;
4912
+ const globalQuota = this.config.globalQuota || null;
4913
+ const allRecords = this.gate.store.getAllRecords();
4914
+ const events = this.gate.meter.getEvents();
4915
+ // ── Summary ──
4916
+ const totalKeys = allRecords.length;
4917
+ let keysWithQuotas = 0;
4918
+ for (const rec of allRecords) {
4919
+ if (rec.quota || globalQuota)
4920
+ keysWithQuotas++;
4921
+ }
4922
+ let totalCalls = 0;
4923
+ let totalQuotaDenials = 0;
4924
+ for (const e of events) {
4925
+ totalCalls++;
4926
+ if (!e.allowed && e.denyReason?.includes('quota_exceeded')) {
4927
+ totalQuotaDenials++;
4928
+ }
4929
+ }
4930
+ const quotaDenialRate = totalCalls > 0
4931
+ ? Math.round(totalQuotaDenials / totalCalls * 10000) / 10000
4932
+ : 0;
4933
+ // ── Per-key breakdown with current quota counters ──
4934
+ const perKey = allRecords.map(rec => {
4935
+ const quota = rec.quota || globalQuota;
4936
+ const dailyCallLimit = quota?.dailyCallLimit || 0;
4937
+ const monthlyCallLimit = quota?.monthlyCallLimit || 0;
4938
+ const dailyCreditLimit = quota?.dailyCreditLimit || 0;
4939
+ const monthlyCreditLimit = quota?.monthlyCreditLimit || 0;
4940
+ const dailyCallUtilization = dailyCallLimit > 0
4941
+ ? Math.round(rec.quotaDailyCalls / dailyCallLimit * 10000) / 10000
4942
+ : 0;
4943
+ const monthlyCallUtilization = monthlyCallLimit > 0
4944
+ ? Math.round(rec.quotaMonthlyCalls / monthlyCallLimit * 10000) / 10000
4945
+ : 0;
4946
+ return {
4947
+ name: rec.name,
4948
+ dailyCalls: rec.quotaDailyCalls,
4949
+ monthlyCalls: rec.quotaMonthlyCalls,
4950
+ dailyCredits: rec.quotaDailyCredits,
4951
+ monthlyCredits: rec.quotaMonthlyCredits,
4952
+ dailyCallLimit,
4953
+ monthlyCallLimit,
4954
+ dailyCreditLimit,
4955
+ monthlyCreditLimit,
4956
+ dailyCallUtilization,
4957
+ monthlyCallUtilization,
4958
+ source: rec.quota ? 'per-key' : (globalQuota ? 'global' : 'none'),
4959
+ };
4960
+ }).sort((a, b) => b.dailyCallUtilization - a.dailyCallUtilization);
4961
+ // ── Per-tool breakdown ──
4962
+ const toolMap = new Map();
4963
+ for (const e of events) {
4964
+ if (!toolMap.has(e.tool))
4965
+ toolMap.set(e.tool, { calls: 0, quotaDenied: 0 });
4966
+ const t = toolMap.get(e.tool);
4967
+ t.calls++;
4968
+ if (!e.allowed && e.denyReason?.includes('quota_exceeded')) {
4969
+ t.quotaDenied++;
4970
+ }
4971
+ }
4972
+ const perTool = Array.from(toolMap.entries())
4973
+ .map(([tool, stats]) => ({ tool, ...stats }))
4974
+ .sort((a, b) => b.quotaDenied - a.quotaDenied);
4975
+ // ── Hourly trends (quota denials) ──
4976
+ const hourBuckets = new Map();
4977
+ for (const e of events) {
4978
+ const hour = e.timestamp.slice(0, 13); // YYYY-MM-DDTHH
4979
+ if (!hourBuckets.has(hour))
4980
+ hourBuckets.set(hour, { calls: 0, quotaDenied: 0 });
4981
+ const h = hourBuckets.get(hour);
4982
+ h.calls++;
4983
+ if (!e.allowed && e.denyReason?.includes('quota_exceeded')) {
4984
+ h.quotaDenied++;
4985
+ }
4986
+ }
4987
+ const hourlyTrends = Array.from(hourBuckets.entries())
4988
+ .map(([hour, stats]) => ({ hour, ...stats }))
4989
+ .sort((a, b) => a.hour.localeCompare(b.hour))
4990
+ .slice(-24);
4991
+ // ── Most constrained keys (by daily call utilization) ──
4992
+ const mostConstrained = perKey
4993
+ .filter(k => k.dailyCallLimit > 0 || k.monthlyCallLimit > 0)
4994
+ .map(k => ({
4995
+ name: k.name,
4996
+ dailyCalls: k.dailyCalls,
4997
+ dailyCallLimit: k.dailyCallLimit,
4998
+ dailyCallUtilization: k.dailyCallUtilization,
4999
+ monthlyCalls: k.monthlyCalls,
5000
+ monthlyCallLimit: k.monthlyCallLimit,
5001
+ monthlyCallUtilization: k.monthlyCallUtilization,
5002
+ }))
5003
+ .sort((a, b) => b.dailyCallUtilization - a.dailyCallUtilization)
5004
+ .slice(0, 10);
5005
+ res.writeHead(200, { 'Content-Type': 'application/json' });
5006
+ res.end(JSON.stringify({
5007
+ config: { globalQuota },
5008
+ summary: { totalKeys, keysWithQuotas, totalQuotaDenials, quotaDenialRate },
5009
+ perKey,
5010
+ perTool,
5011
+ hourlyTrends,
5012
+ mostConstrained,
5013
+ }));
5014
+ }
4786
5015
  // ─── /keys/notes — Timestamped notes on API keys ─────────────────────────
4787
5016
  handleGetNotes(req, res) {
4788
5017
  if (!this.checkAdmin(req, res))