paygate-mcp 8.2.0 → 8.4.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
@@ -109,6 +109,8 @@ Agent → PayGate (auth + billing) → Your MCP Server (stdio or HTTP)
109
109
  - **Key Dashboard** — `GET /keys/dashboard?key=...` consolidated single-endpoint view with metadata, balance, health score, spending velocity, rate limits, quotas, usage summary, and recent activity timeline
110
110
  - **Admin Notifications** — `GET /admin/notifications` scans all keys for actionable issues: expired/expiring keys, zero credits, credit depletion velocity, suspended keys, high error rates, and rate limit pressure — with severity filtering and priority sorting
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
+ - **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
+ - **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
112
114
  - **Config Hot Reload** — `POST /config/reload` reloads pricing, rate limits, webhooks, quotas, and behavior flags from config file without server restart
113
115
  - **Webhook Events** — POST batched usage events to any URL for external billing/alerting
114
116
  - **Config File Mode** — Load all settings from a JSON file (`--config`)
@@ -2467,6 +2469,88 @@ curl http://localhost:3402/admin/dashboard \
2467
2469
 
2468
2470
  Combines key counts by state, credit allocation and spending totals, usage breakdown with deny reasons, top 10 consumers ranked by credits spent, top 10 tools ranked by call count, notification severity counts, and server uptime. Read-only.
2469
2471
 
2472
+ ### Key Lifecycle Report
2473
+
2474
+ Track key creation, revocation, suspension trends and identify at-risk keys:
2475
+
2476
+ ```bash
2477
+ # Full lifecycle report
2478
+ curl http://localhost:3402/admin/lifecycle \
2479
+ -H "X-Admin-Key: YOUR_ADMIN_KEY"
2480
+
2481
+ # Filter by date range
2482
+ curl "http://localhost:3402/admin/lifecycle?since=2026-02-01&until=2026-02-28" \
2483
+ -H "X-Admin-Key: YOUR_ADMIN_KEY"
2484
+ ```
2485
+
2486
+ **Response:**
2487
+
2488
+ ```json
2489
+ {
2490
+ "events": { "created": 25, "revoked": 3, "suspended": 5, "resumed": 4, "rotated": 2, "cloned": 1 },
2491
+ "trends": [
2492
+ { "date": "2026-02-25", "created": 5, "revoked": 1, "suspended": 2, "resumed": 1 },
2493
+ { "date": "2026-02-26", "created": 8, "revoked": 0, "suspended": 1, "resumed": 2 },
2494
+ { "date": "2026-02-27", "created": 12, "revoked": 2, "suspended": 2, "resumed": 1 }
2495
+ ],
2496
+ "averageLifetimeHours": 168.5,
2497
+ "atRisk": [
2498
+ { "key": "pg_c815...09a6", "name": "staging-agent", "risk": "expiring_soon", "details": { "expiresAt": "2026-03-01T...", "daysRemaining": 2.5 } },
2499
+ { "key": "pg_a3f1...b2e4", "name": "batch-worker", "risk": "zero_credits", "details": { "credits": 0 } }
2500
+ ]
2501
+ }
2502
+ ```
2503
+
2504
+ Shows aggregated lifecycle event counts, daily trend buckets (sorted chronologically), average key lifetime in hours (for revoked keys), and at-risk keys with their risk category (`expired`, `expiring_soon`, `zero_credits`). Supports `?since=` and `?until=` date filters. Excludes suspended and revoked keys from at-risk list. Read-only.
2505
+
2506
+ ### Cost Analysis
2507
+
2508
+ Get a cost-centric breakdown of credit usage across tools, namespaces, and time:
2509
+
2510
+ ```bash
2511
+ # Full cost analysis
2512
+ curl http://localhost:3402/admin/costs \
2513
+ -H "X-Admin-Key: YOUR_ADMIN_KEY"
2514
+
2515
+ # Filter by namespace
2516
+ curl "http://localhost:3402/admin/costs?namespace=prod" \
2517
+ -H "X-Admin-Key: YOUR_ADMIN_KEY"
2518
+
2519
+ # Filter by time range
2520
+ curl "http://localhost:3402/admin/costs?since=2026-02-01" \
2521
+ -H "X-Admin-Key: YOUR_ADMIN_KEY"
2522
+ ```
2523
+
2524
+ ```json
2525
+ {
2526
+ "summary": {
2527
+ "totalCredits": 4250,
2528
+ "totalCalls": 312,
2529
+ "totalAllowed": 298,
2530
+ "totalDenied": 14,
2531
+ "avgCostPerCall": 13.62
2532
+ },
2533
+ "perTool": [
2534
+ { "tool": "generate_report", "calls": 85, "credits": 1700, "avgCost": 20 },
2535
+ { "tool": "query_data", "calls": 142, "credits": 1420, "avgCost": 10 }
2536
+ ],
2537
+ "perNamespace": [
2538
+ { "namespace": "prod", "calls": 210, "credits": 3150 },
2539
+ { "namespace": "staging", "calls": 102, "credits": 1100 }
2540
+ ],
2541
+ "hourlyTrends": [
2542
+ { "hour": "2026-02-26T14:00:00.000Z", "calls": 23, "credits": 345, "denied": 1 },
2543
+ { "hour": "2026-02-26T15:00:00.000Z", "calls": 31, "credits": 465, "denied": 0 }
2544
+ ],
2545
+ "topSpenders": [
2546
+ { "key": "pg_a1b2...c3d4", "name": "ml-pipeline", "credits": 1800, "calls": 90 },
2547
+ { "key": "pg_e5f6...g7h8", "name": "batch-worker", "credits": 1200, "calls": 120 }
2548
+ ]
2549
+ }
2550
+ ```
2551
+
2552
+ 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
+
2470
2554
  ### IP Allowlisting
2471
2555
 
2472
2556
  Restrict API keys to specific IP addresses or CIDR ranges:
package/dist/server.d.ts CHANGED
@@ -244,6 +244,8 @@ export declare class PayGateServer {
244
244
  private handleAdminEventStream;
245
245
  private handleAdminNotifications;
246
246
  private handleSystemDashboard;
247
+ private handleKeyLifecycleReport;
248
+ private handleCostAnalysis;
247
249
  private handleGetNotes;
248
250
  private handleAddNote;
249
251
  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
@@ -810,6 +810,18 @@ class PayGateServer {
810
810
  res.writeHead(405, { 'Content-Type': 'application/json' });
811
811
  res.end(JSON.stringify({ error: 'Method not allowed. Use GET.' }));
812
812
  return;
813
+ case '/admin/lifecycle':
814
+ if (req.method === 'GET')
815
+ return this.handleKeyLifecycleReport(req, res);
816
+ res.writeHead(405, { 'Content-Type': 'application/json' });
817
+ res.end(JSON.stringify({ error: 'Method not allowed. Use GET.' }));
818
+ return;
819
+ case '/admin/costs':
820
+ if (req.method === 'GET')
821
+ return this.handleCostAnalysis(req, res);
822
+ res.writeHead(405, { 'Content-Type': 'application/json' });
823
+ res.end(JSON.stringify({ error: 'Method not allowed. Use GET.' }));
824
+ return;
813
825
  // ─── Plugin endpoints ──────────────────────────────────────────────
814
826
  case '/plugins':
815
827
  return this.handleListPlugins(req, res);
@@ -1352,6 +1364,8 @@ class PayGateServer {
1352
1364
  keyDashboard: 'GET /keys/dashboard?key=... — Consolidated key overview with metadata, balance, health, velocity, rate limits, quotas, and recent activity (requires X-Admin-Key)',
1353
1365
  adminNotifications: 'GET /admin/notifications — Actionable notifications for expiring keys, low credits, high error rates, and rate limit pressure (requires X-Admin-Key)',
1354
1366
  systemDashboard: 'GET /admin/dashboard — System-wide overview with key stats, credit summary, usage breakdown, top consumers, and uptime (requires X-Admin-Key)',
1367
+ keyLifecycle: 'GET /admin/lifecycle — Key lifecycle report with creation/revocation/expiry trends, average lifetime, and at-risk keys (requires X-Admin-Key)',
1368
+ costAnalysis: 'GET /admin/costs — Cost analysis with per-tool, per-namespace breakdown, hourly trends, and top spenders (requires X-Admin-Key)',
1355
1369
  ...(this.oauth ? {
1356
1370
  oauthMetadata: 'GET /.well-known/oauth-authorization-server — OAuth 2.1 server metadata',
1357
1371
  oauthRegister: 'POST /oauth/register — Register OAuth client',
@@ -4552,6 +4566,223 @@ class PayGateServer {
4552
4566
  },
4553
4567
  }));
4554
4568
  }
4569
+ // ─── /admin/lifecycle — Key lifecycle report ──────────────────────────────
4570
+ handleKeyLifecycleReport(req, res) {
4571
+ if (!this.checkAdmin(req, res))
4572
+ return;
4573
+ const urlParts = req.url?.split('?') || [];
4574
+ const params = new URLSearchParams(urlParts[1] || '');
4575
+ const since = params.get('since') || undefined;
4576
+ const until = params.get('until') || undefined;
4577
+ // Query audit log for key lifecycle events
4578
+ const createdEvents = this.audit.query({
4579
+ types: ['key.created'], since, until, limit: 10_000,
4580
+ }).events;
4581
+ const revokedEvents = this.audit.query({
4582
+ types: ['key.revoked'], since, until, limit: 10_000,
4583
+ }).events;
4584
+ const suspendedEvents = this.audit.query({
4585
+ types: ['key.suspended'], since, until, limit: 10_000,
4586
+ }).events;
4587
+ const resumedEvents = this.audit.query({
4588
+ types: ['key.resumed'], since, until, limit: 10_000,
4589
+ }).events;
4590
+ const rotatedEvents = this.audit.query({
4591
+ types: ['key.rotated'], since, until, limit: 10_000,
4592
+ }).events;
4593
+ const clonedEvents = this.audit.query({
4594
+ types: ['key.cloned'], since, until, limit: 10_000,
4595
+ }).events;
4596
+ // Build daily buckets for trends
4597
+ const dayBuckets = new Map();
4598
+ const addToBucket = (events, field) => {
4599
+ for (const e of events) {
4600
+ const day = e.timestamp.slice(0, 10); // YYYY-MM-DD
4601
+ if (!dayBuckets.has(day))
4602
+ dayBuckets.set(day, { created: 0, revoked: 0, suspended: 0, resumed: 0 });
4603
+ dayBuckets.get(day)[field]++;
4604
+ }
4605
+ };
4606
+ addToBucket(createdEvents, 'created');
4607
+ addToBucket(revokedEvents, 'revoked');
4608
+ addToBucket(suspendedEvents, 'suspended');
4609
+ addToBucket(resumedEvents, 'resumed');
4610
+ const trends = Array.from(dayBuckets.entries())
4611
+ .map(([date, counts]) => ({ date, ...counts }))
4612
+ .sort((a, b) => a.date.localeCompare(b.date));
4613
+ // Average key lifetime (for revoked keys that have creation metadata)
4614
+ const now = Date.now();
4615
+ const allRecords = this.gate.store.getAllRecords();
4616
+ const lifetimes = [];
4617
+ for (const record of allRecords) {
4618
+ if (!record.active && record.createdAt) {
4619
+ // Revoked key — estimate lifetime from created to now (or expiry)
4620
+ const created = new Date(record.createdAt).getTime();
4621
+ const end = record.expiresAt ? Math.min(new Date(record.expiresAt).getTime(), now) : now;
4622
+ if (created > 0 && end > created) {
4623
+ lifetimes.push(end - created);
4624
+ }
4625
+ }
4626
+ }
4627
+ const avgLifetimeHours = lifetimes.length > 0
4628
+ ? Math.round(lifetimes.reduce((a, b) => a + b, 0) / lifetimes.length / 3_600_000 * 10) / 10
4629
+ : null;
4630
+ // At-risk keys: active keys expiring within 7 days or with zero credits
4631
+ const atRiskKeys = [];
4632
+ for (const record of allRecords) {
4633
+ if (!record.active)
4634
+ continue;
4635
+ if (record.suspended)
4636
+ continue;
4637
+ const maskedKey = record.key.slice(0, 7) + '...' + record.key.slice(-4);
4638
+ const keyName = record.name || undefined;
4639
+ if (record.credits <= 0) {
4640
+ atRiskKeys.push({ key: maskedKey, name: keyName, risk: 'zero_credits', details: { credits: 0 } });
4641
+ }
4642
+ if (record.expiresAt) {
4643
+ const msToExpiry = new Date(record.expiresAt).getTime() - now;
4644
+ if (msToExpiry <= 0) {
4645
+ atRiskKeys.push({ key: maskedKey, name: keyName, risk: 'expired', details: { expiresAt: record.expiresAt } });
4646
+ }
4647
+ else if (msToExpiry <= 7 * 24 * 3_600_000) {
4648
+ const daysLeft = Math.round(msToExpiry / 86_400_000 * 10) / 10;
4649
+ atRiskKeys.push({ key: maskedKey, name: keyName, risk: 'expiring_soon', details: { expiresAt: record.expiresAt, daysRemaining: daysLeft } });
4650
+ }
4651
+ }
4652
+ }
4653
+ res.writeHead(200, { 'Content-Type': 'application/json' });
4654
+ res.end(JSON.stringify({
4655
+ events: {
4656
+ created: createdEvents.length,
4657
+ revoked: revokedEvents.length,
4658
+ suspended: suspendedEvents.length,
4659
+ resumed: resumedEvents.length,
4660
+ rotated: rotatedEvents.length,
4661
+ cloned: clonedEvents.length,
4662
+ },
4663
+ trends,
4664
+ averageLifetimeHours: avgLifetimeHours,
4665
+ atRisk: atRiskKeys,
4666
+ }));
4667
+ }
4668
+ // ─── /admin/costs — Cost analysis ─────────────────────────────────────────
4669
+ handleCostAnalysis(req, res) {
4670
+ if (!this.checkAdmin(req, res))
4671
+ return;
4672
+ const urlParts = req.url?.split('?') || [];
4673
+ const params = new URLSearchParams(urlParts[1] || '');
4674
+ const since = params.get('since') || undefined;
4675
+ const namespace = params.get('namespace') || undefined;
4676
+ // Get all usage events
4677
+ const events = this.gate.meter.getEvents(since, namespace);
4678
+ // ── Per-tool cost breakdown ──
4679
+ const toolCosts = new Map();
4680
+ for (const e of events) {
4681
+ if (!toolCosts.has(e.tool))
4682
+ toolCosts.set(e.tool, { calls: 0, credits: 0, denied: 0, avgCost: 0 });
4683
+ const t = toolCosts.get(e.tool);
4684
+ t.calls++;
4685
+ if (e.allowed) {
4686
+ t.credits += e.creditsCharged;
4687
+ }
4688
+ else {
4689
+ t.denied++;
4690
+ }
4691
+ }
4692
+ for (const [, t] of toolCosts) {
4693
+ const allowed = t.calls - t.denied;
4694
+ t.avgCost = allowed > 0 ? Math.round(t.credits / allowed * 100) / 100 : 0;
4695
+ }
4696
+ const perTool = Array.from(toolCosts.entries())
4697
+ .map(([tool, stats]) => ({ tool, ...stats }))
4698
+ .sort((a, b) => b.credits - a.credits);
4699
+ // ── Per-namespace cost breakdown ──
4700
+ const nsCosts = new Map();
4701
+ for (const e of events) {
4702
+ const ns = e.namespace || 'default';
4703
+ if (!nsCosts.has(ns))
4704
+ nsCosts.set(ns, { calls: 0, credits: 0, denied: 0 });
4705
+ const n = nsCosts.get(ns);
4706
+ n.calls++;
4707
+ if (e.allowed) {
4708
+ n.credits += e.creditsCharged;
4709
+ }
4710
+ else {
4711
+ n.denied++;
4712
+ }
4713
+ }
4714
+ const perNamespace = Array.from(nsCosts.entries())
4715
+ .map(([ns, stats]) => ({ namespace: ns, ...stats }))
4716
+ .sort((a, b) => b.credits - a.credits);
4717
+ // ── Hourly trends (last 24 buckets) ──
4718
+ const hourBuckets = new Map();
4719
+ for (const e of events) {
4720
+ const hour = e.timestamp.slice(0, 13); // YYYY-MM-DDTHH
4721
+ if (!hourBuckets.has(hour))
4722
+ hourBuckets.set(hour, { calls: 0, credits: 0, denied: 0 });
4723
+ const h = hourBuckets.get(hour);
4724
+ h.calls++;
4725
+ if (e.allowed) {
4726
+ h.credits += e.creditsCharged;
4727
+ }
4728
+ else {
4729
+ h.denied++;
4730
+ }
4731
+ }
4732
+ const hourlyTrends = Array.from(hourBuckets.entries())
4733
+ .map(([hour, stats]) => ({ hour, ...stats }))
4734
+ .sort((a, b) => a.hour.localeCompare(b.hour))
4735
+ .slice(-24);
4736
+ // ── Top spenders (by credits) ──
4737
+ const keyCosts = new Map();
4738
+ for (const e of events) {
4739
+ const name = e.keyName || e.apiKey.slice(0, 10);
4740
+ if (!keyCosts.has(name))
4741
+ keyCosts.set(name, { calls: 0, credits: 0, denied: 0 });
4742
+ const k = keyCosts.get(name);
4743
+ k.calls++;
4744
+ if (e.allowed) {
4745
+ k.credits += e.creditsCharged;
4746
+ }
4747
+ else {
4748
+ k.denied++;
4749
+ }
4750
+ }
4751
+ const topSpenders = Array.from(keyCosts.entries())
4752
+ .map(([name, stats]) => ({ name, ...stats }))
4753
+ .sort((a, b) => b.credits - a.credits)
4754
+ .slice(0, 10);
4755
+ // ── Totals ──
4756
+ let totalCredits = 0;
4757
+ let totalCalls = 0;
4758
+ let totalDenied = 0;
4759
+ for (const e of events) {
4760
+ totalCalls++;
4761
+ if (e.allowed) {
4762
+ totalCredits += e.creditsCharged;
4763
+ }
4764
+ else {
4765
+ totalDenied++;
4766
+ }
4767
+ }
4768
+ const avgCostPerCall = totalCalls - totalDenied > 0
4769
+ ? Math.round(totalCredits / (totalCalls - totalDenied) * 100) / 100
4770
+ : 0;
4771
+ res.writeHead(200, { 'Content-Type': 'application/json' });
4772
+ res.end(JSON.stringify({
4773
+ summary: {
4774
+ totalCredits,
4775
+ totalCalls,
4776
+ totalAllowed: totalCalls - totalDenied,
4777
+ totalDenied,
4778
+ avgCostPerCall,
4779
+ },
4780
+ perTool,
4781
+ perNamespace,
4782
+ hourlyTrends,
4783
+ topSpenders,
4784
+ }));
4785
+ }
4555
4786
  // ─── /keys/notes — Timestamped notes on API keys ─────────────────────────
4556
4787
  handleGetNotes(req, res) {
4557
4788
  if (!this.checkAdmin(req, res))