mcp-cost-tracker 1.0.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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -0
  3. package/dist/dashboard/generator.d.ts +54 -0
  4. package/dist/dashboard/generator.d.ts.map +1 -0
  5. package/dist/dashboard/generator.js +577 -0
  6. package/dist/dashboard/generator.js.map +1 -0
  7. package/dist/index.d.ts +12 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +60 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/pricing/models.d.ts +48 -0
  12. package/dist/pricing/models.d.ts.map +1 -0
  13. package/dist/pricing/models.js +207 -0
  14. package/dist/pricing/models.js.map +1 -0
  15. package/dist/storage/database.d.ts +129 -0
  16. package/dist/storage/database.d.ts.map +1 -0
  17. package/dist/storage/database.js +374 -0
  18. package/dist/storage/database.js.map +1 -0
  19. package/dist/tools/index.d.ts +4 -0
  20. package/dist/tools/index.d.ts.map +1 -0
  21. package/dist/tools/index.js +660 -0
  22. package/dist/tools/index.js.map +1 -0
  23. package/dist/tools/prompts.d.ts +3 -0
  24. package/dist/tools/prompts.d.ts.map +1 -0
  25. package/dist/tools/prompts.js +111 -0
  26. package/dist/tools/prompts.js.map +1 -0
  27. package/dist/tools/resources.d.ts +4 -0
  28. package/dist/tools/resources.d.ts.map +1 -0
  29. package/dist/tools/resources.js +138 -0
  30. package/dist/tools/resources.js.map +1 -0
  31. package/dist/utils/helpers.d.ts +29 -0
  32. package/dist/utils/helpers.d.ts.map +1 -0
  33. package/dist/utils/helpers.js +81 -0
  34. package/dist/utils/helpers.js.map +1 -0
  35. package/package.json +52 -0
  36. package/src/dashboard/generator.ts +628 -0
  37. package/src/index.ts +73 -0
  38. package/src/pricing/models.ts +246 -0
  39. package/src/storage/database.ts +525 -0
  40. package/src/tools/index.ts +780 -0
  41. package/src/tools/prompts.ts +124 -0
  42. package/src/tools/resources.ts +171 -0
  43. package/src/utils/helpers.ts +71 -0
  44. package/tsconfig.json +20 -0
@@ -0,0 +1,628 @@
1
+ import { CostDatabase } from '../storage/database.js';
2
+
3
+ export interface DashboardData {
4
+ summary: {
5
+ total_cost: number;
6
+ total_input_tokens: number;
7
+ total_output_tokens: number;
8
+ total_cached_tokens: number;
9
+ total_requests: number;
10
+ avg_cost_per_request: number;
11
+ };
12
+ costByModel: Array<{
13
+ provider: string;
14
+ model: string;
15
+ total_cost: number;
16
+ total_input_tokens: number;
17
+ total_output_tokens: number;
18
+ request_count: number;
19
+ }>;
20
+ costByProvider: Array<{
21
+ provider: string;
22
+ total_cost: number;
23
+ total_input_tokens: number;
24
+ total_output_tokens: number;
25
+ request_count: number;
26
+ }>;
27
+ costByDay: Array<{
28
+ date: string;
29
+ total_cost: number;
30
+ total_tokens: number;
31
+ request_count: number;
32
+ }>;
33
+ costByProject: Array<{
34
+ project: string;
35
+ total_cost: number;
36
+ total_tokens: number;
37
+ request_count: number;
38
+ }>;
39
+ topExpensive: Array<{
40
+ timestamp: string;
41
+ provider: string;
42
+ model: string;
43
+ total_cost: number;
44
+ total_tokens: number;
45
+ description: string;
46
+ }>;
47
+ generatedAt: string;
48
+ }
49
+
50
+ export function collectDashboardData(db: CostDatabase, days: number = 30): DashboardData {
51
+ return {
52
+ summary: db.getSummary(),
53
+ costByModel: db.getCostByModel(),
54
+ costByProvider: db.getCostByProvider(),
55
+ costByDay: db.getCostByDay({ days }),
56
+ costByProject: db.getCostByProject(),
57
+ topExpensive: db.getTopExpensiveRequests(10).map(r => ({
58
+ timestamp: r.timestamp,
59
+ provider: r.provider,
60
+ model: r.model,
61
+ total_cost: r.total_cost,
62
+ total_tokens: r.total_tokens,
63
+ description: r.description,
64
+ })),
65
+ generatedAt: new Date().toISOString(),
66
+ };
67
+ }
68
+
69
+ export function generateDashboardHTML(data: DashboardData): string {
70
+ const providerColors: Record<string, string> = {
71
+ openai: '#10a37f',
72
+ anthropic: '#d4a574',
73
+ google: '#4285f4',
74
+ deepseek: '#0066ff',
75
+ mistral: '#ff7000',
76
+ meta: '#0668E1',
77
+ cohere: '#39594d',
78
+ xai: '#1DA1F2',
79
+ };
80
+
81
+ const getColor = (provider: string, alpha: number = 1) => {
82
+ const hex = providerColors[provider.toLowerCase()] || '#6b7280';
83
+ const r = parseInt(hex.slice(1, 3), 16);
84
+ const g = parseInt(hex.slice(3, 5), 16);
85
+ const b = parseInt(hex.slice(5, 7), 16);
86
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
87
+ };
88
+
89
+ const formatCost = (cost: number) => `$${cost.toFixed(4)}`;
90
+ const formatTokens = (tokens: number) => {
91
+ if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(2)}M`;
92
+ if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}K`;
93
+ return tokens.toString();
94
+ };
95
+
96
+ return `<!DOCTYPE html>
97
+ <html lang="en" data-theme="dark">
98
+ <head>
99
+ <meta charset="UTF-8">
100
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
101
+ <title>MCP Cost Tracker Dashboard</title>
102
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
103
+ <style>
104
+ :root {
105
+ --bg-primary: #0f172a;
106
+ --bg-secondary: #1e293b;
107
+ --bg-card: #1e293b;
108
+ --bg-card-hover: #334155;
109
+ --text-primary: #f1f5f9;
110
+ --text-secondary: #94a3b8;
111
+ --text-muted: #64748b;
112
+ --border: #334155;
113
+ --accent: #3b82f6;
114
+ --accent-green: #10b981;
115
+ --accent-red: #ef4444;
116
+ --accent-yellow: #f59e0b;
117
+ --accent-purple: #8b5cf6;
118
+ --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
119
+ }
120
+
121
+ * { margin: 0; padding: 0; box-sizing: border-box; }
122
+
123
+ body {
124
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
125
+ background: var(--bg-primary);
126
+ color: var(--text-primary);
127
+ line-height: 1.6;
128
+ min-height: 100vh;
129
+ }
130
+
131
+ .container {
132
+ max-width: 1400px;
133
+ margin: 0 auto;
134
+ padding: 24px;
135
+ }
136
+
137
+ header {
138
+ text-align: center;
139
+ padding: 32px 0;
140
+ border-bottom: 1px solid var(--border);
141
+ margin-bottom: 32px;
142
+ }
143
+
144
+ header h1 {
145
+ font-size: 2.5rem;
146
+ font-weight: 800;
147
+ background: linear-gradient(135deg, #3b82f6, #8b5cf6, #ec4899);
148
+ -webkit-background-clip: text;
149
+ -webkit-text-fill-color: transparent;
150
+ margin-bottom: 8px;
151
+ }
152
+
153
+ header p {
154
+ color: var(--text-secondary);
155
+ font-size: 0.95rem;
156
+ }
157
+
158
+ .stats-grid {
159
+ display: grid;
160
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
161
+ gap: 16px;
162
+ margin-bottom: 32px;
163
+ }
164
+
165
+ .stat-card {
166
+ background: var(--bg-card);
167
+ border: 1px solid var(--border);
168
+ border-radius: 12px;
169
+ padding: 24px;
170
+ transition: all 0.2s;
171
+ }
172
+
173
+ .stat-card:hover {
174
+ background: var(--bg-card-hover);
175
+ transform: translateY(-2px);
176
+ box-shadow: var(--shadow);
177
+ }
178
+
179
+ .stat-card .label {
180
+ font-size: 0.8rem;
181
+ text-transform: uppercase;
182
+ letter-spacing: 0.05em;
183
+ color: var(--text-muted);
184
+ margin-bottom: 8px;
185
+ }
186
+
187
+ .stat-card .value {
188
+ font-size: 1.8rem;
189
+ font-weight: 700;
190
+ }
191
+
192
+ .stat-card .sub {
193
+ font-size: 0.85rem;
194
+ color: var(--text-secondary);
195
+ margin-top: 4px;
196
+ }
197
+
198
+ .cost-value { color: var(--accent-green); }
199
+ .token-value { color: var(--accent); }
200
+ .request-value { color: var(--accent-purple); }
201
+ .avg-value { color: var(--accent-yellow); }
202
+
203
+ .charts-grid {
204
+ display: grid;
205
+ grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
206
+ gap: 24px;
207
+ margin-bottom: 32px;
208
+ }
209
+
210
+ .chart-card {
211
+ background: var(--bg-card);
212
+ border: 1px solid var(--border);
213
+ border-radius: 12px;
214
+ padding: 24px;
215
+ }
216
+
217
+ .chart-card h3 {
218
+ font-size: 1.1rem;
219
+ margin-bottom: 16px;
220
+ color: var(--text-primary);
221
+ }
222
+
223
+ .chart-container {
224
+ position: relative;
225
+ height: 300px;
226
+ }
227
+
228
+ table {
229
+ width: 100%;
230
+ border-collapse: collapse;
231
+ font-size: 0.9rem;
232
+ }
233
+
234
+ th {
235
+ text-align: left;
236
+ padding: 12px 16px;
237
+ border-bottom: 2px solid var(--border);
238
+ color: var(--text-muted);
239
+ font-weight: 600;
240
+ text-transform: uppercase;
241
+ font-size: 0.75rem;
242
+ letter-spacing: 0.05em;
243
+ }
244
+
245
+ td {
246
+ padding: 12px 16px;
247
+ border-bottom: 1px solid var(--border);
248
+ color: var(--text-secondary);
249
+ }
250
+
251
+ tr:hover td {
252
+ background: var(--bg-card-hover);
253
+ }
254
+
255
+ .provider-badge {
256
+ display: inline-block;
257
+ padding: 2px 10px;
258
+ border-radius: 9999px;
259
+ font-size: 0.75rem;
260
+ font-weight: 600;
261
+ text-transform: uppercase;
262
+ }
263
+
264
+ .table-card {
265
+ background: var(--bg-card);
266
+ border: 1px solid var(--border);
267
+ border-radius: 12px;
268
+ padding: 24px;
269
+ margin-bottom: 24px;
270
+ overflow-x: auto;
271
+ }
272
+
273
+ .table-card h3 {
274
+ font-size: 1.1rem;
275
+ margin-bottom: 16px;
276
+ }
277
+
278
+ footer {
279
+ text-align: center;
280
+ padding: 24px 0;
281
+ border-top: 1px solid var(--border);
282
+ color: var(--text-muted);
283
+ font-size: 0.85rem;
284
+ }
285
+
286
+ @media (max-width: 768px) {
287
+ .charts-grid { grid-template-columns: 1fr; }
288
+ .container { padding: 16px; }
289
+ header h1 { font-size: 1.8rem; }
290
+ }
291
+ </style>
292
+ </head>
293
+ <body>
294
+ <div class="container">
295
+ <header>
296
+ <h1>MCP Cost Tracker</h1>
297
+ <p>LLM API Usage & Cost Dashboard — Generated ${data.generatedAt}</p>
298
+ </header>
299
+
300
+ <div class="stats-grid">
301
+ <div class="stat-card">
302
+ <div class="label">Total Spend</div>
303
+ <div class="value cost-value">${formatCost(data.summary.total_cost)}</div>
304
+ <div class="sub">${data.summary.total_requests} API calls</div>
305
+ </div>
306
+ <div class="stat-card">
307
+ <div class="label">Input Tokens</div>
308
+ <div class="value token-value">${formatTokens(data.summary.total_input_tokens)}</div>
309
+ <div class="sub">prompt tokens consumed</div>
310
+ </div>
311
+ <div class="stat-card">
312
+ <div class="label">Output Tokens</div>
313
+ <div class="value token-value">${formatTokens(data.summary.total_output_tokens)}</div>
314
+ <div class="sub">completion tokens generated</div>
315
+ </div>
316
+ <div class="stat-card">
317
+ <div class="label">Cached Tokens</div>
318
+ <div class="value request-value">${formatTokens(data.summary.total_cached_tokens)}</div>
319
+ <div class="sub">tokens served from cache</div>
320
+ </div>
321
+ <div class="stat-card">
322
+ <div class="label">Avg Cost / Request</div>
323
+ <div class="value avg-value">${formatCost(data.summary.avg_cost_per_request)}</div>
324
+ <div class="sub">per API call</div>
325
+ </div>
326
+ </div>
327
+
328
+ <div class="charts-grid">
329
+ <div class="chart-card">
330
+ <h3>Daily Cost Trend</h3>
331
+ <div class="chart-container">
332
+ <canvas id="dailyCostChart"></canvas>
333
+ </div>
334
+ </div>
335
+ <div class="chart-card">
336
+ <h3>Cost by Provider</h3>
337
+ <div class="chart-container">
338
+ <canvas id="providerChart"></canvas>
339
+ </div>
340
+ </div>
341
+ <div class="chart-card">
342
+ <h3>Cost by Model</h3>
343
+ <div class="chart-container">
344
+ <canvas id="modelChart"></canvas>
345
+ </div>
346
+ </div>
347
+ <div class="chart-card">
348
+ <h3>Cost by Project</h3>
349
+ <div class="chart-container">
350
+ <canvas id="projectChart"></canvas>
351
+ </div>
352
+ </div>
353
+ </div>
354
+
355
+ <div class="table-card">
356
+ <h3>Cost Breakdown by Model</h3>
357
+ <table>
358
+ <thead>
359
+ <tr>
360
+ <th>Provider</th>
361
+ <th>Model</th>
362
+ <th>Requests</th>
363
+ <th>Input Tokens</th>
364
+ <th>Output Tokens</th>
365
+ <th>Total Cost</th>
366
+ </tr>
367
+ </thead>
368
+ <tbody>
369
+ ${data.costByModel.map(m => `
370
+ <tr>
371
+ <td><span class="provider-badge" style="background:${getColor(m.provider, 0.2)};color:${getColor(m.provider)}">${m.provider}</span></td>
372
+ <td>${m.model}</td>
373
+ <td>${m.request_count}</td>
374
+ <td>${formatTokens(m.total_input_tokens)}</td>
375
+ <td>${formatTokens(m.total_output_tokens)}</td>
376
+ <td style="color:var(--accent-green);font-weight:600">${formatCost(m.total_cost)}</td>
377
+ </tr>`).join('')}
378
+ </tbody>
379
+ </table>
380
+ </div>
381
+
382
+ <div class="table-card">
383
+ <h3>Top 10 Most Expensive Requests</h3>
384
+ <table>
385
+ <thead>
386
+ <tr>
387
+ <th>Timestamp</th>
388
+ <th>Provider</th>
389
+ <th>Model</th>
390
+ <th>Tokens</th>
391
+ <th>Cost</th>
392
+ <th>Description</th>
393
+ </tr>
394
+ </thead>
395
+ <tbody>
396
+ ${data.topExpensive.map(r => `
397
+ <tr>
398
+ <td>${r.timestamp}</td>
399
+ <td><span class="provider-badge" style="background:${getColor(r.provider, 0.2)};color:${getColor(r.provider)}">${r.provider}</span></td>
400
+ <td>${r.model}</td>
401
+ <td>${formatTokens(r.total_tokens)}</td>
402
+ <td style="color:var(--accent-green);font-weight:600">${formatCost(r.total_cost)}</td>
403
+ <td>${r.description || '—'}</td>
404
+ </tr>`).join('')}
405
+ </tbody>
406
+ </table>
407
+ </div>
408
+
409
+ <footer>
410
+ <p>Generated by <strong>mcp-cost-tracker</strong> — Open source MCP server for LLM cost tracking</p>
411
+ </footer>
412
+ </div>
413
+
414
+ <script>
415
+ Chart.defaults.color = '#94a3b8';
416
+ Chart.defaults.borderColor = '#334155';
417
+
418
+ // Daily Cost Trend
419
+ new Chart(document.getElementById('dailyCostChart'), {
420
+ type: 'line',
421
+ data: {
422
+ labels: ${JSON.stringify(data.costByDay.map(d => d.date))},
423
+ datasets: [{
424
+ label: 'Daily Cost ($)',
425
+ data: ${JSON.stringify(data.costByDay.map(d => d.total_cost))},
426
+ borderColor: '#3b82f6',
427
+ backgroundColor: 'rgba(59, 130, 246, 0.1)',
428
+ fill: true,
429
+ tension: 0.4,
430
+ pointRadius: 4,
431
+ pointHoverRadius: 6,
432
+ }]
433
+ },
434
+ options: {
435
+ responsive: true,
436
+ maintainAspectRatio: false,
437
+ plugins: {
438
+ legend: { display: false },
439
+ tooltip: {
440
+ callbacks: {
441
+ label: ctx => '$' + ctx.parsed.y.toFixed(4)
442
+ }
443
+ }
444
+ },
445
+ scales: {
446
+ y: {
447
+ beginAtZero: true,
448
+ ticks: { callback: v => '$' + v.toFixed(2) },
449
+ grid: { color: '#1e293b' }
450
+ },
451
+ x: { grid: { display: false } }
452
+ }
453
+ }
454
+ });
455
+
456
+ // Provider Doughnut
457
+ new Chart(document.getElementById('providerChart'), {
458
+ type: 'doughnut',
459
+ data: {
460
+ labels: ${JSON.stringify(data.costByProvider.map(p => p.provider))},
461
+ datasets: [{
462
+ data: ${JSON.stringify(data.costByProvider.map(p => p.total_cost))},
463
+ backgroundColor: ${JSON.stringify(data.costByProvider.map(p => getColor(p.provider, 0.8)))},
464
+ borderColor: ${JSON.stringify(data.costByProvider.map(p => getColor(p.provider)))},
465
+ borderWidth: 2,
466
+ }]
467
+ },
468
+ options: {
469
+ responsive: true,
470
+ maintainAspectRatio: false,
471
+ plugins: {
472
+ tooltip: {
473
+ callbacks: {
474
+ label: ctx => ctx.label + ': $' + ctx.parsed.toFixed(4)
475
+ }
476
+ },
477
+ legend: {
478
+ position: 'right',
479
+ labels: { padding: 16, usePointStyle: true }
480
+ }
481
+ }
482
+ }
483
+ });
484
+
485
+ // Model Bar Chart
486
+ const modelData = ${JSON.stringify(data.costByModel.slice(0, 10))};
487
+ new Chart(document.getElementById('modelChart'), {
488
+ type: 'bar',
489
+ data: {
490
+ labels: modelData.map(m => m.model),
491
+ datasets: [{
492
+ label: 'Cost ($)',
493
+ data: modelData.map(m => m.total_cost),
494
+ backgroundColor: modelData.map(m => {
495
+ const colors = ${JSON.stringify(providerColors)};
496
+ const hex = colors[m.provider.toLowerCase()] || '#6b7280';
497
+ return hex + 'cc';
498
+ }),
499
+ borderRadius: 6,
500
+ }]
501
+ },
502
+ options: {
503
+ responsive: true,
504
+ maintainAspectRatio: false,
505
+ indexAxis: 'y',
506
+ plugins: {
507
+ legend: { display: false },
508
+ tooltip: {
509
+ callbacks: {
510
+ label: ctx => '$' + ctx.parsed.x.toFixed(4)
511
+ }
512
+ }
513
+ },
514
+ scales: {
515
+ x: {
516
+ beginAtZero: true,
517
+ ticks: { callback: v => '$' + v.toFixed(2) },
518
+ grid: { color: '#1e293b' }
519
+ },
520
+ y: { grid: { display: false } }
521
+ }
522
+ }
523
+ });
524
+
525
+ // Project Chart
526
+ const projectData = ${JSON.stringify(data.costByProject)};
527
+ new Chart(document.getElementById('projectChart'), {
528
+ type: 'bar',
529
+ data: {
530
+ labels: projectData.map(p => p.project),
531
+ datasets: [{
532
+ label: 'Cost ($)',
533
+ data: projectData.map(p => p.total_cost),
534
+ backgroundColor: ['#3b82f6cc', '#8b5cf6cc', '#ec4899cc', '#f59e0bcc', '#10b981cc', '#06b6d4cc'],
535
+ borderRadius: 6,
536
+ }]
537
+ },
538
+ options: {
539
+ responsive: true,
540
+ maintainAspectRatio: false,
541
+ plugins: {
542
+ legend: { display: false },
543
+ tooltip: {
544
+ callbacks: {
545
+ label: ctx => '$' + ctx.parsed.y.toFixed(4)
546
+ }
547
+ }
548
+ },
549
+ scales: {
550
+ y: {
551
+ beginAtZero: true,
552
+ ticks: { callback: v => '$' + v.toFixed(2) },
553
+ grid: { color: '#1e293b' }
554
+ },
555
+ x: { grid: { display: false } }
556
+ }
557
+ }
558
+ });
559
+ </script>
560
+ </body>
561
+ </html>`;
562
+ }
563
+
564
+ /**
565
+ * Generate a Markdown text report (for terminal / MCP response).
566
+ */
567
+ export function generateTextReport(data: DashboardData): string {
568
+ const formatCost = (cost: number) => `$${cost.toFixed(4)}`;
569
+ const formatTokens = (tokens: number) => {
570
+ if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(2)}M`;
571
+ if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}K`;
572
+ return tokens.toString();
573
+ };
574
+
575
+ let report = `# MCP Cost Tracker Report\n`;
576
+ report += `Generated: ${data.generatedAt}\n\n`;
577
+
578
+ report += `## Summary\n`;
579
+ report += `| Metric | Value |\n|--------|-------|\n`;
580
+ report += `| Total Spend | ${formatCost(data.summary.total_cost)} |\n`;
581
+ report += `| Total Requests | ${data.summary.total_requests.toLocaleString()} |\n`;
582
+ report += `| Input Tokens | ${formatTokens(data.summary.total_input_tokens)} |\n`;
583
+ report += `| Output Tokens | ${formatTokens(data.summary.total_output_tokens)} |\n`;
584
+ report += `| Cached Tokens | ${formatTokens(data.summary.total_cached_tokens)} |\n`;
585
+ report += `| Avg Cost/Request | ${formatCost(data.summary.avg_cost_per_request)} |\n\n`;
586
+
587
+ if (data.costByProvider.length > 0) {
588
+ report += `## Cost by Provider\n`;
589
+ report += `| Provider | Cost | Requests | Input Tokens | Output Tokens |\n`;
590
+ report += `|----------|------|----------|--------------|---------------|\n`;
591
+ for (const p of data.costByProvider) {
592
+ report += `| ${p.provider} | ${formatCost(p.total_cost)} | ${p.request_count} | ${formatTokens(p.total_input_tokens)} | ${formatTokens(p.total_output_tokens)} |\n`;
593
+ }
594
+ report += '\n';
595
+ }
596
+
597
+ if (data.costByModel.length > 0) {
598
+ report += `## Cost by Model\n`;
599
+ report += `| Provider | Model | Cost | Requests |\n`;
600
+ report += `|----------|-------|------|----------|\n`;
601
+ for (const m of data.costByModel) {
602
+ report += `| ${m.provider} | ${m.model} | ${formatCost(m.total_cost)} | ${m.request_count} |\n`;
603
+ }
604
+ report += '\n';
605
+ }
606
+
607
+ if (data.costByProject.length > 0) {
608
+ report += `## Cost by Project\n`;
609
+ report += `| Project | Cost | Requests | Tokens |\n`;
610
+ report += `|---------|------|----------|--------|\n`;
611
+ for (const p of data.costByProject) {
612
+ report += `| ${p.project} | ${formatCost(p.total_cost)} | ${p.request_count} | ${formatTokens(p.total_tokens)} |\n`;
613
+ }
614
+ report += '\n';
615
+ }
616
+
617
+ if (data.costByDay.length > 0) {
618
+ report += `## Daily Trend (Last ${data.costByDay.length} days)\n`;
619
+ report += `| Date | Cost | Requests | Tokens |\n`;
620
+ report += `|------|------|----------|--------|\n`;
621
+ for (const d of data.costByDay) {
622
+ report += `| ${d.date} | ${formatCost(d.total_cost)} | ${d.request_count} | ${formatTokens(d.total_tokens)} |\n`;
623
+ }
624
+ report += '\n';
625
+ }
626
+
627
+ return report;
628
+ }
package/src/index.ts ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * MCP Cost Tracker
5
+ *
6
+ * An MCP server that automatically tracks LLM API call costs,
7
+ * token usage, and generates visual dashboards.
8
+ *
9
+ * Supports 50+ models across OpenAI, Anthropic, Google, DeepSeek,
10
+ * Mistral, Meta, Cohere, xAI, and more.
11
+ */
12
+
13
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
14
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
15
+ import { CostDatabase } from './storage/database.js';
16
+ import { registerAllTools } from './tools/index.js';
17
+ import { registerResources } from './tools/resources.js';
18
+ import { registerPrompts } from './tools/prompts.js';
19
+
20
+ // ============================================================
21
+ // Configuration
22
+ // ============================================================
23
+
24
+ const DB_PATH = process.env.MCP_COST_TRACKER_DB || undefined;
25
+
26
+ // ============================================================
27
+ // Server Initialization
28
+ // ============================================================
29
+
30
+ const server = new McpServer(
31
+ {
32
+ name: 'mcp-cost-tracker',
33
+ version: '1.0.0',
34
+ },
35
+ {
36
+ capabilities: {
37
+ logging: {},
38
+ },
39
+ }
40
+ );
41
+
42
+ // Initialize database
43
+ const db = new CostDatabase(DB_PATH);
44
+
45
+ // Register all tools, resources, and prompts
46
+ registerAllTools(server, db);
47
+ registerResources(server, db);
48
+ registerPrompts(server);
49
+
50
+ // ============================================================
51
+ // Transport & Startup
52
+ // ============================================================
53
+
54
+ async function main(): Promise<void> {
55
+ const transport = new StdioServerTransport();
56
+ await server.connect(transport);
57
+
58
+ // Handle graceful shutdown
59
+ process.on('SIGINT', () => {
60
+ db.close();
61
+ process.exit(0);
62
+ });
63
+
64
+ process.on('SIGTERM', () => {
65
+ db.close();
66
+ process.exit(0);
67
+ });
68
+ }
69
+
70
+ main().catch((error) => {
71
+ console.error('Fatal error:', error);
72
+ process.exit(1);
73
+ });