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.
- package/LICENSE +21 -0
- package/README.md +99 -0
- package/dist/dashboard/generator.d.ts +54 -0
- package/dist/dashboard/generator.d.ts.map +1 -0
- package/dist/dashboard/generator.js +577 -0
- package/dist/dashboard/generator.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/pricing/models.d.ts +48 -0
- package/dist/pricing/models.d.ts.map +1 -0
- package/dist/pricing/models.js +207 -0
- package/dist/pricing/models.js.map +1 -0
- package/dist/storage/database.d.ts +129 -0
- package/dist/storage/database.d.ts.map +1 -0
- package/dist/storage/database.js +374 -0
- package/dist/storage/database.js.map +1 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +660 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/prompts.d.ts +3 -0
- package/dist/tools/prompts.d.ts.map +1 -0
- package/dist/tools/prompts.js +111 -0
- package/dist/tools/prompts.js.map +1 -0
- package/dist/tools/resources.d.ts +4 -0
- package/dist/tools/resources.d.ts.map +1 -0
- package/dist/tools/resources.js +138 -0
- package/dist/tools/resources.js.map +1 -0
- package/dist/utils/helpers.d.ts +29 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +81 -0
- package/dist/utils/helpers.js.map +1 -0
- package/package.json +52 -0
- package/src/dashboard/generator.ts +628 -0
- package/src/index.ts +73 -0
- package/src/pricing/models.ts +246 -0
- package/src/storage/database.ts +525 -0
- package/src/tools/index.ts +780 -0
- package/src/tools/prompts.ts +124 -0
- package/src/tools/resources.ts +171 -0
- package/src/utils/helpers.ts +71 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerAllTools = registerAllTools;
|
|
7
|
+
const zod_1 = require("zod");
|
|
8
|
+
const models_js_1 = require("../pricing/models.js");
|
|
9
|
+
const generator_js_1 = require("../dashboard/generator.js");
|
|
10
|
+
const helpers_js_1 = require("../utils/helpers.js");
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const os_1 = __importDefault(require("os"));
|
|
14
|
+
function registerAllTools(server, db) {
|
|
15
|
+
// ============================================================
|
|
16
|
+
// 1. track_usage ā Record an API call's token usage and cost
|
|
17
|
+
// ============================================================
|
|
18
|
+
server.registerTool('track_usage', {
|
|
19
|
+
title: 'Track API Usage',
|
|
20
|
+
description: 'Record an LLM API call with token counts. Automatically calculates cost based on built-in pricing for 50+ models across OpenAI, Anthropic, Google, DeepSeek, Mistral, Meta, and more.',
|
|
21
|
+
inputSchema: zod_1.z.object({
|
|
22
|
+
provider: zod_1.z.string().describe('LLM provider name (e.g., openai, anthropic, google, deepseek, mistral)'),
|
|
23
|
+
model: zod_1.z.string().describe('Model identifier (e.g., gpt-4o, claude-sonnet-4, gemini-2.5-flash)'),
|
|
24
|
+
input_tokens: zod_1.z.number().int().min(0).describe('Number of input/prompt tokens'),
|
|
25
|
+
output_tokens: zod_1.z.number().int().min(0).describe('Number of output/completion tokens'),
|
|
26
|
+
cached_input_tokens: zod_1.z.number().int().min(0).optional().default(0).describe('Number of cached input tokens (if applicable)'),
|
|
27
|
+
session_id: zod_1.z.string().optional().default('').describe('Session or conversation ID for grouping'),
|
|
28
|
+
project: zod_1.z.string().optional().default('default').describe('Project name for categorization'),
|
|
29
|
+
description: zod_1.z.string().optional().default('').describe('Optional description of the API call'),
|
|
30
|
+
metadata: zod_1.z.string().optional().default('{}').describe('Optional JSON metadata'),
|
|
31
|
+
}),
|
|
32
|
+
annotations: {
|
|
33
|
+
title: 'Track API Usage',
|
|
34
|
+
readOnlyHint: false,
|
|
35
|
+
destructiveHint: false,
|
|
36
|
+
idempotentHint: false,
|
|
37
|
+
openWorldHint: false,
|
|
38
|
+
},
|
|
39
|
+
}, async (args) => {
|
|
40
|
+
const { provider, model, input_tokens, output_tokens, cached_input_tokens, session_id, project, description, metadata } = args;
|
|
41
|
+
const costResult = (0, models_js_1.calculateCost)(model, input_tokens, output_tokens, provider, cached_input_tokens);
|
|
42
|
+
const record = db.recordUsage({
|
|
43
|
+
timestamp: new Date().toISOString(),
|
|
44
|
+
provider: provider.toLowerCase(),
|
|
45
|
+
model,
|
|
46
|
+
input_tokens,
|
|
47
|
+
output_tokens,
|
|
48
|
+
cached_input_tokens: cached_input_tokens || 0,
|
|
49
|
+
total_tokens: input_tokens + output_tokens,
|
|
50
|
+
input_cost: costResult.inputCost,
|
|
51
|
+
output_cost: costResult.outputCost,
|
|
52
|
+
cached_input_cost: costResult.cachedInputCost,
|
|
53
|
+
total_cost: costResult.cost,
|
|
54
|
+
session_id: session_id || '',
|
|
55
|
+
project: project || 'default',
|
|
56
|
+
description: description || '',
|
|
57
|
+
metadata: metadata || '{}',
|
|
58
|
+
});
|
|
59
|
+
const pricingInfo = costResult.pricing
|
|
60
|
+
? `Pricing: ${costResult.pricing.model} @ $${costResult.pricing.inputPricePerMTok}/$${costResult.pricing.outputPricePerMTok} per MTok`
|
|
61
|
+
: 'ā ļø No pricing found for this model. Cost recorded as $0. Use set_custom_pricing to add pricing.';
|
|
62
|
+
return {
|
|
63
|
+
content: [{
|
|
64
|
+
type: 'text',
|
|
65
|
+
text: [
|
|
66
|
+
`ā
Usage tracked successfully (ID: ${record.id})`,
|
|
67
|
+
``,
|
|
68
|
+
`Provider: ${provider}`,
|
|
69
|
+
`Model: ${model}`,
|
|
70
|
+
`Input tokens: ${(0, helpers_js_1.formatTokens)(input_tokens)}`,
|
|
71
|
+
`Output tokens: ${(0, helpers_js_1.formatTokens)(output_tokens)}`,
|
|
72
|
+
cached_input_tokens ? `Cached tokens: ${(0, helpers_js_1.formatTokens)(cached_input_tokens)}` : null,
|
|
73
|
+
``,
|
|
74
|
+
`š° Cost breakdown:`,
|
|
75
|
+
` Input cost: ${(0, helpers_js_1.formatCost)(costResult.inputCost)}`,
|
|
76
|
+
` Output cost: ${(0, helpers_js_1.formatCost)(costResult.outputCost)}`,
|
|
77
|
+
costResult.cachedInputCost > 0 ? ` Cache cost: ${(0, helpers_js_1.formatCost)(costResult.cachedInputCost)}` : null,
|
|
78
|
+
` Total cost: ${(0, helpers_js_1.formatCost)(costResult.cost)}`,
|
|
79
|
+
``,
|
|
80
|
+
pricingInfo,
|
|
81
|
+
].filter(Boolean).join('\n'),
|
|
82
|
+
}],
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
// ============================================================
|
|
86
|
+
// 2. get_summary ā Get cost summary with filters
|
|
87
|
+
// ============================================================
|
|
88
|
+
server.registerTool('get_summary', {
|
|
89
|
+
title: 'Get Cost Summary',
|
|
90
|
+
description: 'Get a summary of total costs, tokens, and request counts. Filter by date range, provider, or project.',
|
|
91
|
+
inputSchema: zod_1.z.object({
|
|
92
|
+
start_date: zod_1.z.string().optional().describe('Start date (ISO 8601 format, e.g., 2025-01-01)'),
|
|
93
|
+
end_date: zod_1.z.string().optional().describe('End date (ISO 8601 format)'),
|
|
94
|
+
provider: zod_1.z.string().optional().describe('Filter by provider'),
|
|
95
|
+
project: zod_1.z.string().optional().describe('Filter by project'),
|
|
96
|
+
}),
|
|
97
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
98
|
+
}, async (args) => {
|
|
99
|
+
const summary = db.getSummary({
|
|
100
|
+
startDate: args.start_date,
|
|
101
|
+
endDate: args.end_date,
|
|
102
|
+
provider: args.provider,
|
|
103
|
+
project: args.project,
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
content: [{
|
|
107
|
+
type: 'text',
|
|
108
|
+
text: [
|
|
109
|
+
`š Cost Summary`,
|
|
110
|
+
args.start_date || args.end_date ? `Period: ${args.start_date || 'ā'} ā ${args.end_date || 'now'}` : 'Period: All time',
|
|
111
|
+
args.provider ? `Provider: ${args.provider}` : null,
|
|
112
|
+
args.project ? `Project: ${args.project}` : null,
|
|
113
|
+
``,
|
|
114
|
+
`| Metric | Value |`,
|
|
115
|
+
`|--------|-------|`,
|
|
116
|
+
`| Total Spend | ${(0, helpers_js_1.formatCost)(summary.total_cost)} |`,
|
|
117
|
+
`| Total Requests | ${summary.total_requests.toLocaleString()} |`,
|
|
118
|
+
`| Input Tokens | ${(0, helpers_js_1.formatTokens)(summary.total_input_tokens)} |`,
|
|
119
|
+
`| Output Tokens | ${(0, helpers_js_1.formatTokens)(summary.total_output_tokens)} |`,
|
|
120
|
+
`| Cached Tokens | ${(0, helpers_js_1.formatTokens)(summary.total_cached_tokens)} |`,
|
|
121
|
+
`| Avg Cost/Request | ${(0, helpers_js_1.formatCost)(summary.avg_cost_per_request)} |`,
|
|
122
|
+
].filter(Boolean).join('\n'),
|
|
123
|
+
}],
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
// ============================================================
|
|
127
|
+
// 3. get_cost_by_model ā Breakdown by model
|
|
128
|
+
// ============================================================
|
|
129
|
+
server.registerTool('get_cost_by_model', {
|
|
130
|
+
title: 'Cost by Model',
|
|
131
|
+
description: 'Get cost breakdown grouped by model. See which models are costing you the most.',
|
|
132
|
+
inputSchema: zod_1.z.object({
|
|
133
|
+
start_date: zod_1.z.string().optional().describe('Start date filter'),
|
|
134
|
+
end_date: zod_1.z.string().optional().describe('End date filter'),
|
|
135
|
+
project: zod_1.z.string().optional().describe('Filter by project'),
|
|
136
|
+
}),
|
|
137
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
138
|
+
}, async (args) => {
|
|
139
|
+
const data = db.getCostByModel({
|
|
140
|
+
startDate: args.start_date,
|
|
141
|
+
endDate: args.end_date,
|
|
142
|
+
project: args.project,
|
|
143
|
+
});
|
|
144
|
+
if (data.length === 0) {
|
|
145
|
+
return { content: [{ type: 'text', text: 'No usage data found for the specified filters.' }] };
|
|
146
|
+
}
|
|
147
|
+
let table = `š Cost by Model\n\n`;
|
|
148
|
+
table += `| Provider | Model | Cost | Requests | Input Tokens | Output Tokens |\n`;
|
|
149
|
+
table += `|----------|-------|------|----------|--------------|---------------|\n`;
|
|
150
|
+
for (const m of data) {
|
|
151
|
+
table += `| ${m.provider} | ${m.model} | ${(0, helpers_js_1.formatCost)(m.total_cost)} | ${m.request_count} | ${(0, helpers_js_1.formatTokens)(m.total_input_tokens)} | ${(0, helpers_js_1.formatTokens)(m.total_output_tokens)} |\n`;
|
|
152
|
+
}
|
|
153
|
+
return { content: [{ type: 'text', text: table }] };
|
|
154
|
+
});
|
|
155
|
+
// ============================================================
|
|
156
|
+
// 4. get_cost_by_provider ā Breakdown by provider
|
|
157
|
+
// ============================================================
|
|
158
|
+
server.registerTool('get_cost_by_provider', {
|
|
159
|
+
title: 'Cost by Provider',
|
|
160
|
+
description: 'Get cost breakdown grouped by provider (OpenAI, Anthropic, Google, etc.).',
|
|
161
|
+
inputSchema: zod_1.z.object({
|
|
162
|
+
start_date: zod_1.z.string().optional().describe('Start date filter'),
|
|
163
|
+
end_date: zod_1.z.string().optional().describe('End date filter'),
|
|
164
|
+
}),
|
|
165
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
166
|
+
}, async (args) => {
|
|
167
|
+
const data = db.getCostByProvider({
|
|
168
|
+
startDate: args.start_date,
|
|
169
|
+
endDate: args.end_date,
|
|
170
|
+
});
|
|
171
|
+
if (data.length === 0) {
|
|
172
|
+
return { content: [{ type: 'text', text: 'No usage data found.' }] };
|
|
173
|
+
}
|
|
174
|
+
let table = `š Cost by Provider\n\n`;
|
|
175
|
+
table += `| Provider | Cost | Requests | Input Tokens | Output Tokens |\n`;
|
|
176
|
+
table += `|----------|------|----------|--------------|---------------|\n`;
|
|
177
|
+
for (const p of data) {
|
|
178
|
+
table += `| ${p.provider} | ${(0, helpers_js_1.formatCost)(p.total_cost)} | ${p.request_count} | ${(0, helpers_js_1.formatTokens)(p.total_input_tokens)} | ${(0, helpers_js_1.formatTokens)(p.total_output_tokens)} |\n`;
|
|
179
|
+
}
|
|
180
|
+
return { content: [{ type: 'text', text: table }] };
|
|
181
|
+
});
|
|
182
|
+
// ============================================================
|
|
183
|
+
// 5. get_daily_trend ā Daily cost trend
|
|
184
|
+
// ============================================================
|
|
185
|
+
server.registerTool('get_daily_trend', {
|
|
186
|
+
title: 'Daily Cost Trend',
|
|
187
|
+
description: 'Get daily cost trend over a specified number of days. Useful for spotting spending patterns.',
|
|
188
|
+
inputSchema: zod_1.z.object({
|
|
189
|
+
days: zod_1.z.number().int().min(1).max(365).optional().default(30).describe('Number of days to look back (default: 30)'),
|
|
190
|
+
provider: zod_1.z.string().optional().describe('Filter by provider'),
|
|
191
|
+
project: zod_1.z.string().optional().describe('Filter by project'),
|
|
192
|
+
}),
|
|
193
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
194
|
+
}, async (args) => {
|
|
195
|
+
const data = db.getCostByDay({
|
|
196
|
+
days: args.days,
|
|
197
|
+
provider: args.provider,
|
|
198
|
+
project: args.project,
|
|
199
|
+
});
|
|
200
|
+
if (data.length === 0) {
|
|
201
|
+
return { content: [{ type: 'text', text: 'No usage data found for the specified period.' }] };
|
|
202
|
+
}
|
|
203
|
+
let table = `š Daily Cost Trend (Last ${args.days} days)\n\n`;
|
|
204
|
+
table += `| Date | Cost | Requests | Tokens |\n`;
|
|
205
|
+
table += `|------|------|----------|--------|\n`;
|
|
206
|
+
for (const d of data) {
|
|
207
|
+
table += `| ${d.date} | ${(0, helpers_js_1.formatCost)(d.total_cost)} | ${d.request_count} | ${(0, helpers_js_1.formatTokens)(d.total_tokens)} |\n`;
|
|
208
|
+
}
|
|
209
|
+
const totalCost = data.reduce((sum, d) => sum + d.total_cost, 0);
|
|
210
|
+
const avgDaily = totalCost / data.length;
|
|
211
|
+
table += `\nTotal: ${(0, helpers_js_1.formatCost)(totalCost)} | Avg/day: ${(0, helpers_js_1.formatCost)(avgDaily)}`;
|
|
212
|
+
return { content: [{ type: 'text', text: table }] };
|
|
213
|
+
});
|
|
214
|
+
// ============================================================
|
|
215
|
+
// 6. set_budget ā Create or update a budget
|
|
216
|
+
// ============================================================
|
|
217
|
+
server.registerTool('set_budget', {
|
|
218
|
+
title: 'Set Budget',
|
|
219
|
+
description: 'Create or update a spending budget with alerts. Get notified when approaching or exceeding limits.',
|
|
220
|
+
inputSchema: zod_1.z.object({
|
|
221
|
+
name: zod_1.z.string().describe('Budget name (unique identifier)'),
|
|
222
|
+
limit: zod_1.z.number().positive().describe('Budget limit in USD'),
|
|
223
|
+
period: zod_1.z.enum(['daily', 'weekly', 'monthly', 'total']).optional().default('monthly').describe('Budget period'),
|
|
224
|
+
provider: zod_1.z.string().optional().default('').describe('Filter budget to specific provider'),
|
|
225
|
+
model: zod_1.z.string().optional().default('').describe('Filter budget to specific model'),
|
|
226
|
+
project: zod_1.z.string().optional().default('').describe('Filter budget to specific project'),
|
|
227
|
+
}),
|
|
228
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
229
|
+
}, async (args) => {
|
|
230
|
+
const budget = db.setBudget({
|
|
231
|
+
name: args.name,
|
|
232
|
+
limit_amount: args.limit,
|
|
233
|
+
period: args.period || 'monthly',
|
|
234
|
+
provider_filter: args.provider || '',
|
|
235
|
+
model_filter: args.model || '',
|
|
236
|
+
project_filter: args.project || '',
|
|
237
|
+
});
|
|
238
|
+
const status = db.checkBudget(args.name);
|
|
239
|
+
return {
|
|
240
|
+
content: [{
|
|
241
|
+
type: 'text',
|
|
242
|
+
text: [
|
|
243
|
+
`ā
Budget "${args.name}" set successfully`,
|
|
244
|
+
``,
|
|
245
|
+
`Limit: ${(0, helpers_js_1.formatCost)(args.limit)} / ${args.period}`,
|
|
246
|
+
args.provider ? `Provider filter: ${args.provider}` : null,
|
|
247
|
+
args.model ? `Model filter: ${args.model}` : null,
|
|
248
|
+
args.project ? `Project filter: ${args.project}` : null,
|
|
249
|
+
``,
|
|
250
|
+
status ? `Current spend: ${(0, helpers_js_1.formatCost)(status.spent)} (${status.percentage.toFixed(1)}%)` : null,
|
|
251
|
+
status ? `Remaining: ${(0, helpers_js_1.formatCost)(status.remaining)}` : null,
|
|
252
|
+
status?.exceeded ? `ā ļø BUDGET EXCEEDED!` : null,
|
|
253
|
+
].filter(Boolean).join('\n'),
|
|
254
|
+
}],
|
|
255
|
+
};
|
|
256
|
+
});
|
|
257
|
+
// ============================================================
|
|
258
|
+
// 7. check_budget ā Check budget status
|
|
259
|
+
// ============================================================
|
|
260
|
+
server.registerTool('check_budget', {
|
|
261
|
+
title: 'Check Budget',
|
|
262
|
+
description: 'Check the current status of all budgets or a specific budget. Shows spend vs limit.',
|
|
263
|
+
inputSchema: zod_1.z.object({
|
|
264
|
+
name: zod_1.z.string().optional().describe('Budget name to check (leave empty for all budgets)'),
|
|
265
|
+
}),
|
|
266
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
267
|
+
}, async (args) => {
|
|
268
|
+
if (args.name) {
|
|
269
|
+
const status = db.checkBudget(args.name);
|
|
270
|
+
if (!status) {
|
|
271
|
+
return { content: [{ type: 'text', text: `Budget "${args.name}" not found.` }] };
|
|
272
|
+
}
|
|
273
|
+
const bar = generateProgressBar(status.percentage);
|
|
274
|
+
return {
|
|
275
|
+
content: [{
|
|
276
|
+
type: 'text',
|
|
277
|
+
text: [
|
|
278
|
+
`š° Budget: ${status.budget.name}`,
|
|
279
|
+
`Period: ${status.budget.period}`,
|
|
280
|
+
``,
|
|
281
|
+
`${bar} ${status.percentage.toFixed(1)}%`,
|
|
282
|
+
`Spent: ${(0, helpers_js_1.formatCost)(status.spent)} / ${(0, helpers_js_1.formatCost)(status.budget.limit_amount)}`,
|
|
283
|
+
`Remaining: ${(0, helpers_js_1.formatCost)(status.remaining)}`,
|
|
284
|
+
``,
|
|
285
|
+
status.exceeded ? `šØ BUDGET EXCEEDED!` : status.percentage >= 80 ? `ā ļø Warning: Approaching budget limit!` : `ā
Within budget`,
|
|
286
|
+
].join('\n'),
|
|
287
|
+
}],
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
// Show all budgets
|
|
291
|
+
const budgets = db.getBudgets();
|
|
292
|
+
if (budgets.length === 0) {
|
|
293
|
+
return { content: [{ type: 'text', text: 'No budgets configured. Use set_budget to create one.' }] };
|
|
294
|
+
}
|
|
295
|
+
let result = `š° Budget Status Overview\n\n`;
|
|
296
|
+
result += `| Budget | Period | Spent | Limit | % | Status |\n`;
|
|
297
|
+
result += `|--------|--------|-------|-------|---|--------|\n`;
|
|
298
|
+
for (const b of budgets) {
|
|
299
|
+
const status = db.checkBudget(b.name);
|
|
300
|
+
if (status) {
|
|
301
|
+
const statusIcon = status.exceeded ? 'šØ' : status.percentage >= 80 ? 'ā ļø' : 'ā
';
|
|
302
|
+
result += `| ${b.name} | ${b.period} | ${(0, helpers_js_1.formatCost)(status.spent)} | ${(0, helpers_js_1.formatCost)(b.limit_amount)} | ${status.percentage.toFixed(0)}% | ${statusIcon} |\n`;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return { content: [{ type: 'text', text: result }] };
|
|
306
|
+
});
|
|
307
|
+
// ============================================================
|
|
308
|
+
// 8. generate_dashboard ā Generate HTML dashboard
|
|
309
|
+
// ============================================================
|
|
310
|
+
server.registerTool('generate_dashboard', {
|
|
311
|
+
title: 'Generate Dashboard',
|
|
312
|
+
description: 'Generate a beautiful HTML dashboard with charts showing cost trends, breakdowns by model/provider/project, and top expensive requests.',
|
|
313
|
+
inputSchema: zod_1.z.object({
|
|
314
|
+
days: zod_1.z.number().int().min(1).max(365).optional().default(30).describe('Number of days to include in the dashboard'),
|
|
315
|
+
output_path: zod_1.z.string().optional().describe('Custom output path for the HTML file'),
|
|
316
|
+
format: zod_1.z.enum(['html', 'text']).optional().default('html').describe('Output format: html for visual dashboard, text for markdown report'),
|
|
317
|
+
}),
|
|
318
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
319
|
+
}, async (args) => {
|
|
320
|
+
const data = (0, generator_js_1.collectDashboardData)(db, args.days);
|
|
321
|
+
if (args.format === 'text') {
|
|
322
|
+
const report = (0, generator_js_1.generateTextReport)(data);
|
|
323
|
+
return { content: [{ type: 'text', text: report }] };
|
|
324
|
+
}
|
|
325
|
+
const html = (0, generator_js_1.generateDashboardHTML)(data);
|
|
326
|
+
const outputDir = path_1.default.join(os_1.default.homedir(), '.mcp-cost-tracker');
|
|
327
|
+
if (!fs_1.default.existsSync(outputDir)) {
|
|
328
|
+
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
329
|
+
}
|
|
330
|
+
const outputPath = args.output_path || path_1.default.join(outputDir, 'dashboard.html');
|
|
331
|
+
fs_1.default.writeFileSync(outputPath, html, 'utf-8');
|
|
332
|
+
return {
|
|
333
|
+
content: [{
|
|
334
|
+
type: 'text',
|
|
335
|
+
text: [
|
|
336
|
+
`š Dashboard generated successfully!`,
|
|
337
|
+
``,
|
|
338
|
+
`File: ${outputPath}`,
|
|
339
|
+
`Period: Last ${args.days} days`,
|
|
340
|
+
`Total records: ${data.summary.total_requests}`,
|
|
341
|
+
`Total spend: ${(0, helpers_js_1.formatCost)(data.summary.total_cost)}`,
|
|
342
|
+
``,
|
|
343
|
+
`Open the HTML file in a browser to view interactive charts.`,
|
|
344
|
+
].join('\n'),
|
|
345
|
+
}],
|
|
346
|
+
};
|
|
347
|
+
});
|
|
348
|
+
// ============================================================
|
|
349
|
+
// 9. lookup_pricing ā Look up model pricing
|
|
350
|
+
// ============================================================
|
|
351
|
+
server.registerTool('lookup_pricing', {
|
|
352
|
+
title: 'Lookup Model Pricing',
|
|
353
|
+
description: 'Look up pricing for a specific model or list all supported models and their prices.',
|
|
354
|
+
inputSchema: zod_1.z.object({
|
|
355
|
+
model: zod_1.z.string().optional().describe('Model name to look up (leave empty to list all)'),
|
|
356
|
+
provider: zod_1.z.string().optional().describe('Provider name to filter by'),
|
|
357
|
+
}),
|
|
358
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
359
|
+
}, async (args) => {
|
|
360
|
+
if (args.model) {
|
|
361
|
+
const pricing = (0, models_js_1.lookupPricing)(args.model, args.provider);
|
|
362
|
+
if (!pricing) {
|
|
363
|
+
return {
|
|
364
|
+
content: [{
|
|
365
|
+
type: 'text',
|
|
366
|
+
text: `No pricing found for model "${args.model}". Use set_custom_pricing to add it, or check supported models with lookup_pricing (no arguments).`,
|
|
367
|
+
}],
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
content: [{
|
|
372
|
+
type: 'text',
|
|
373
|
+
text: [
|
|
374
|
+
`š² Pricing: ${pricing.provider} / ${pricing.model}`,
|
|
375
|
+
``,
|
|
376
|
+
`| Metric | Price |`,
|
|
377
|
+
`|--------|-------|`,
|
|
378
|
+
`| Input | $${pricing.inputPricePerMTok.toFixed(4)} / MTok |`,
|
|
379
|
+
`| Output | $${pricing.outputPricePerMTok.toFixed(4)} / MTok |`,
|
|
380
|
+
pricing.cachedInputPricePerMTok ? `| Cached Input | $${pricing.cachedInputPricePerMTok.toFixed(4)} / MTok |` : null,
|
|
381
|
+
pricing.contextWindow ? `| Context Window | ${(0, helpers_js_1.formatTokens)(pricing.contextWindow)} |` : null,
|
|
382
|
+
`| Category | ${pricing.category} |`,
|
|
383
|
+
pricing.notes ? `\nNote: ${pricing.notes}` : null,
|
|
384
|
+
].filter(Boolean).join('\n'),
|
|
385
|
+
}],
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
// List all models
|
|
389
|
+
if (args.provider) {
|
|
390
|
+
const models = (0, models_js_1.getModelsForProvider)(args.provider);
|
|
391
|
+
if (models.length === 0) {
|
|
392
|
+
return {
|
|
393
|
+
content: [{
|
|
394
|
+
type: 'text',
|
|
395
|
+
text: `No models found for provider "${args.provider}". Supported providers: ${(0, models_js_1.getSupportedProviders)().join(', ')}`,
|
|
396
|
+
}],
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
let table = `š² ${args.provider} Models\n\n`;
|
|
400
|
+
table += `| Model | Input $/MTok | Output $/MTok | Category |\n`;
|
|
401
|
+
table += `|-------|-------------|---------------|----------|\n`;
|
|
402
|
+
for (const m of models) {
|
|
403
|
+
table += `| ${m.model} | $${m.inputPricePerMTok.toFixed(4)} | $${m.outputPricePerMTok.toFixed(4)} | ${m.category} |\n`;
|
|
404
|
+
}
|
|
405
|
+
return { content: [{ type: 'text', text: table }] };
|
|
406
|
+
}
|
|
407
|
+
// List all providers
|
|
408
|
+
const providers = (0, models_js_1.getSupportedProviders)();
|
|
409
|
+
let result = `š² Supported Providers & Models\n\n`;
|
|
410
|
+
result += `Providers: ${providers.join(', ')}\n`;
|
|
411
|
+
result += `Total models: ${(0, models_js_1.getAllModels)().length}\n\n`;
|
|
412
|
+
result += `Use \`lookup_pricing\` with a provider name to see all models for that provider.\n`;
|
|
413
|
+
result += `Use \`lookup_pricing\` with a model name to see detailed pricing.\n`;
|
|
414
|
+
return { content: [{ type: 'text', text: result }] };
|
|
415
|
+
});
|
|
416
|
+
// ============================================================
|
|
417
|
+
// 10. set_custom_pricing ā Add custom model pricing
|
|
418
|
+
// ============================================================
|
|
419
|
+
server.registerTool('set_custom_pricing', {
|
|
420
|
+
title: 'Set Custom Pricing',
|
|
421
|
+
description: 'Add or update custom pricing for a model not in the built-in database, or override existing pricing.',
|
|
422
|
+
inputSchema: zod_1.z.object({
|
|
423
|
+
provider: zod_1.z.string().describe('Provider name'),
|
|
424
|
+
model: zod_1.z.string().describe('Model identifier'),
|
|
425
|
+
input_price: zod_1.z.number().min(0).describe('Input price per million tokens (USD)'),
|
|
426
|
+
output_price: zod_1.z.number().min(0).describe('Output price per million tokens (USD)'),
|
|
427
|
+
cached_input_price: zod_1.z.number().min(0).optional().default(0).describe('Cached input price per million tokens (USD)'),
|
|
428
|
+
}),
|
|
429
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
430
|
+
}, async (args) => {
|
|
431
|
+
db.setCustomPricing({
|
|
432
|
+
provider: args.provider.toLowerCase(),
|
|
433
|
+
model: args.model,
|
|
434
|
+
input_price_per_mtok: args.input_price,
|
|
435
|
+
output_price_per_mtok: args.output_price,
|
|
436
|
+
cached_input_price_per_mtok: args.cached_input_price || 0,
|
|
437
|
+
});
|
|
438
|
+
return {
|
|
439
|
+
content: [{
|
|
440
|
+
type: 'text',
|
|
441
|
+
text: [
|
|
442
|
+
`ā
Custom pricing set for ${args.provider}/${args.model}`,
|
|
443
|
+
``,
|
|
444
|
+
`Input: $${args.input_price.toFixed(4)} / MTok`,
|
|
445
|
+
`Output: $${args.output_price.toFixed(4)} / MTok`,
|
|
446
|
+
args.cached_input_price ? `Cached: $${args.cached_input_price.toFixed(4)} / MTok` : null,
|
|
447
|
+
].filter(Boolean).join('\n'),
|
|
448
|
+
}],
|
|
449
|
+
};
|
|
450
|
+
});
|
|
451
|
+
// ============================================================
|
|
452
|
+
// 11. estimate_cost ā Estimate cost before making an API call
|
|
453
|
+
// ============================================================
|
|
454
|
+
server.registerTool('estimate_cost', {
|
|
455
|
+
title: 'Estimate Cost',
|
|
456
|
+
description: 'Estimate the cost of an API call before making it. Useful for pre-flight cost checks.',
|
|
457
|
+
inputSchema: zod_1.z.object({
|
|
458
|
+
model: zod_1.z.string().describe('Model identifier'),
|
|
459
|
+
input_tokens: zod_1.z.number().int().min(0).describe('Estimated input tokens'),
|
|
460
|
+
output_tokens: zod_1.z.number().int().min(0).describe('Estimated output tokens'),
|
|
461
|
+
provider: zod_1.z.string().optional().describe('Provider name'),
|
|
462
|
+
requests: zod_1.z.number().int().min(1).optional().default(1).describe('Number of requests to estimate'),
|
|
463
|
+
}),
|
|
464
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
465
|
+
}, async (args) => {
|
|
466
|
+
const costResult = (0, models_js_1.calculateCost)(args.model, args.input_tokens, args.output_tokens, args.provider);
|
|
467
|
+
if (!costResult.pricing) {
|
|
468
|
+
return {
|
|
469
|
+
content: [{
|
|
470
|
+
type: 'text',
|
|
471
|
+
text: `No pricing found for model "${args.model}". Use lookup_pricing to see available models.`,
|
|
472
|
+
}],
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
const totalForRequests = costResult.cost * (args.requests || 1);
|
|
476
|
+
return {
|
|
477
|
+
content: [{
|
|
478
|
+
type: 'text',
|
|
479
|
+
text: [
|
|
480
|
+
`š® Cost Estimate: ${costResult.pricing.provider}/${costResult.pricing.model}`,
|
|
481
|
+
``,
|
|
482
|
+
`Per request:`,
|
|
483
|
+
` Input (${(0, helpers_js_1.formatTokens)(args.input_tokens)} tokens): ${(0, helpers_js_1.formatCost)(costResult.inputCost)}`,
|
|
484
|
+
` Output (${(0, helpers_js_1.formatTokens)(args.output_tokens)} tokens): ${(0, helpers_js_1.formatCost)(costResult.outputCost)}`,
|
|
485
|
+
` Total: ${(0, helpers_js_1.formatCost)(costResult.cost)}`,
|
|
486
|
+
args.requests && args.requests > 1 ? `\nFor ${args.requests} requests: ${(0, helpers_js_1.formatCost)(totalForRequests)}` : null,
|
|
487
|
+
``,
|
|
488
|
+
`Pricing: $${costResult.pricing.inputPricePerMTok}/$${costResult.pricing.outputPricePerMTok} per MTok`,
|
|
489
|
+
].filter(Boolean).join('\n'),
|
|
490
|
+
}],
|
|
491
|
+
};
|
|
492
|
+
});
|
|
493
|
+
// ============================================================
|
|
494
|
+
// 12. compare_models ā Compare costs between models
|
|
495
|
+
// ============================================================
|
|
496
|
+
server.registerTool('compare_models', {
|
|
497
|
+
title: 'Compare Model Costs',
|
|
498
|
+
description: 'Compare the cost of the same workload across different models. Helps find the most cost-effective option.',
|
|
499
|
+
inputSchema: zod_1.z.object({
|
|
500
|
+
models: zod_1.z.array(zod_1.z.string()).min(2).max(10).describe('List of model names to compare'),
|
|
501
|
+
input_tokens: zod_1.z.number().int().min(0).describe('Input tokens for comparison'),
|
|
502
|
+
output_tokens: zod_1.z.number().int().min(0).describe('Output tokens for comparison'),
|
|
503
|
+
}),
|
|
504
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
505
|
+
}, async (args) => {
|
|
506
|
+
const results = args.models.map(model => {
|
|
507
|
+
const cost = (0, models_js_1.calculateCost)(model, args.input_tokens, args.output_tokens);
|
|
508
|
+
return {
|
|
509
|
+
model,
|
|
510
|
+
provider: cost.pricing?.provider || 'unknown',
|
|
511
|
+
cost: cost.cost,
|
|
512
|
+
pricing: cost.pricing,
|
|
513
|
+
};
|
|
514
|
+
}).sort((a, b) => a.cost - b.cost);
|
|
515
|
+
let table = `āļø Model Cost Comparison\n`;
|
|
516
|
+
table += `Workload: ${(0, helpers_js_1.formatTokens)(args.input_tokens)} input + ${(0, helpers_js_1.formatTokens)(args.output_tokens)} output tokens\n\n`;
|
|
517
|
+
table += `| # | Provider | Model | Cost | vs Cheapest |\n`;
|
|
518
|
+
table += `|---|----------|-------|------|-------------|\n`;
|
|
519
|
+
const cheapest = results[0]?.cost || 0;
|
|
520
|
+
results.forEach((r, i) => {
|
|
521
|
+
const diff = cheapest > 0 ? ((r.cost - cheapest) / cheapest * 100).toFixed(0) : '0';
|
|
522
|
+
const marker = i === 0 ? 'š' : '';
|
|
523
|
+
table += `| ${i + 1} | ${r.provider} | ${r.model} | ${(0, helpers_js_1.formatCost)(r.cost)} | ${i === 0 ? 'baseline' : `+${diff}%`} ${marker} |\n`;
|
|
524
|
+
});
|
|
525
|
+
if (results.length >= 2) {
|
|
526
|
+
const savings = results[results.length - 1].cost - results[0].cost;
|
|
527
|
+
table += `\nš” Switching from ${results[results.length - 1].model} to ${results[0].model} saves ${(0, helpers_js_1.formatCost)(savings)} per request.`;
|
|
528
|
+
}
|
|
529
|
+
return { content: [{ type: 'text', text: table }] };
|
|
530
|
+
});
|
|
531
|
+
// ============================================================
|
|
532
|
+
// 13. get_recent_usage ā Get recent usage records
|
|
533
|
+
// ============================================================
|
|
534
|
+
server.registerTool('get_recent_usage', {
|
|
535
|
+
title: 'Recent Usage',
|
|
536
|
+
description: 'Get the most recent API usage records with details.',
|
|
537
|
+
inputSchema: zod_1.z.object({
|
|
538
|
+
limit: zod_1.z.number().int().min(1).max(100).optional().default(10).describe('Number of records to return'),
|
|
539
|
+
provider: zod_1.z.string().optional().describe('Filter by provider'),
|
|
540
|
+
model: zod_1.z.string().optional().describe('Filter by model'),
|
|
541
|
+
project: zod_1.z.string().optional().describe('Filter by project'),
|
|
542
|
+
}),
|
|
543
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
544
|
+
}, async (args) => {
|
|
545
|
+
const records = db.getUsageRecords({
|
|
546
|
+
provider: args.provider,
|
|
547
|
+
model: args.model,
|
|
548
|
+
project: args.project,
|
|
549
|
+
limit: args.limit,
|
|
550
|
+
});
|
|
551
|
+
if (records.length === 0) {
|
|
552
|
+
return { content: [{ type: 'text', text: 'No usage records found.' }] };
|
|
553
|
+
}
|
|
554
|
+
let table = `š Recent Usage (${records.length} records)\n\n`;
|
|
555
|
+
table += `| Time | Provider | Model | Tokens | Cost | Project |\n`;
|
|
556
|
+
table += `|------|----------|-------|--------|------|---------|\n`;
|
|
557
|
+
for (const r of records) {
|
|
558
|
+
table += `| ${r.timestamp} | ${r.provider} | ${r.model} | ${(0, helpers_js_1.formatTokens)(r.total_tokens)} | ${(0, helpers_js_1.formatCost)(r.total_cost)} | ${r.project} |\n`;
|
|
559
|
+
}
|
|
560
|
+
return { content: [{ type: 'text', text: table }] };
|
|
561
|
+
});
|
|
562
|
+
// ============================================================
|
|
563
|
+
// 14. export_data ā Export usage data
|
|
564
|
+
// ============================================================
|
|
565
|
+
server.registerTool('export_data', {
|
|
566
|
+
title: 'Export Data',
|
|
567
|
+
description: 'Export usage data as CSV or JSON for external analysis.',
|
|
568
|
+
inputSchema: zod_1.z.object({
|
|
569
|
+
format: zod_1.z.enum(['csv', 'json']).optional().default('csv').describe('Export format'),
|
|
570
|
+
start_date: zod_1.z.string().optional().describe('Start date filter'),
|
|
571
|
+
end_date: zod_1.z.string().optional().describe('End date filter'),
|
|
572
|
+
output_path: zod_1.z.string().optional().describe('Custom output file path'),
|
|
573
|
+
}),
|
|
574
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
575
|
+
}, async (args) => {
|
|
576
|
+
const records = db.getUsageRecords({
|
|
577
|
+
startDate: args.start_date,
|
|
578
|
+
endDate: args.end_date,
|
|
579
|
+
});
|
|
580
|
+
const outputDir = path_1.default.join(os_1.default.homedir(), '.mcp-cost-tracker');
|
|
581
|
+
if (!fs_1.default.existsSync(outputDir)) {
|
|
582
|
+
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
583
|
+
}
|
|
584
|
+
let outputPath;
|
|
585
|
+
let content;
|
|
586
|
+
if (args.format === 'json') {
|
|
587
|
+
outputPath = args.output_path || path_1.default.join(outputDir, `export_${Date.now()}.json`);
|
|
588
|
+
content = JSON.stringify(records, null, 2);
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
outputPath = args.output_path || path_1.default.join(outputDir, `export_${Date.now()}.csv`);
|
|
592
|
+
const headers = ['timestamp', 'provider', 'model', 'input_tokens', 'output_tokens', 'cached_input_tokens', 'total_tokens', 'input_cost', 'output_cost', 'total_cost', 'session_id', 'project', 'description'];
|
|
593
|
+
const rows = records.map(r => headers.map(h => {
|
|
594
|
+
const val = r[h];
|
|
595
|
+
return typeof val === 'string' && val.includes(',') ? `"${val}"` : val;
|
|
596
|
+
}).join(','));
|
|
597
|
+
content = [headers.join(','), ...rows].join('\n');
|
|
598
|
+
}
|
|
599
|
+
fs_1.default.writeFileSync(outputPath, content, 'utf-8');
|
|
600
|
+
return {
|
|
601
|
+
content: [{
|
|
602
|
+
type: 'text',
|
|
603
|
+
text: [
|
|
604
|
+
`š Data exported successfully!`,
|
|
605
|
+
``,
|
|
606
|
+
`File: ${outputPath}`,
|
|
607
|
+
`Format: ${args.format?.toUpperCase()}`,
|
|
608
|
+
`Records: ${records.length}`,
|
|
609
|
+
].join('\n'),
|
|
610
|
+
}],
|
|
611
|
+
};
|
|
612
|
+
});
|
|
613
|
+
// ============================================================
|
|
614
|
+
// 15. get_stats ā Get database statistics
|
|
615
|
+
// ============================================================
|
|
616
|
+
server.registerTool('get_stats', {
|
|
617
|
+
title: 'Database Stats',
|
|
618
|
+
description: 'Get statistics about the cost tracking database.',
|
|
619
|
+
inputSchema: zod_1.z.object({}),
|
|
620
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
621
|
+
}, async () => {
|
|
622
|
+
const stats = db.getStats();
|
|
623
|
+
const budgets = db.getBudgets();
|
|
624
|
+
const customPricing = db.getCustomPricing();
|
|
625
|
+
const providers = (0, models_js_1.getSupportedProviders)();
|
|
626
|
+
const formatBytes = (bytes) => {
|
|
627
|
+
if (bytes >= 1_000_000)
|
|
628
|
+
return `${(bytes / 1_000_000).toFixed(2)} MB`;
|
|
629
|
+
if (bytes >= 1_000)
|
|
630
|
+
return `${(bytes / 1_000).toFixed(1)} KB`;
|
|
631
|
+
return `${bytes} bytes`;
|
|
632
|
+
};
|
|
633
|
+
return {
|
|
634
|
+
content: [{
|
|
635
|
+
type: 'text',
|
|
636
|
+
text: [
|
|
637
|
+
`š MCP Cost Tracker Stats`,
|
|
638
|
+
``,
|
|
639
|
+
`| Metric | Value |`,
|
|
640
|
+
`|--------|-------|`,
|
|
641
|
+
`| Total Records | ${stats.total_records.toLocaleString()} |`,
|
|
642
|
+
`| First Record | ${stats.first_record || 'N/A'} |`,
|
|
643
|
+
`| Last Record | ${stats.last_record || 'N/A'} |`,
|
|
644
|
+
`| Database Size | ${formatBytes(stats.db_size_bytes)} |`,
|
|
645
|
+
`| Budgets Configured | ${budgets.length} |`,
|
|
646
|
+
`| Custom Pricing Rules | ${customPricing.length} |`,
|
|
647
|
+
`| Built-in Models | ${(0, models_js_1.getAllModels)().length} |`,
|
|
648
|
+
`| Supported Providers | ${providers.join(', ')} |`,
|
|
649
|
+
].join('\n'),
|
|
650
|
+
}],
|
|
651
|
+
};
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
function generateProgressBar(percentage) {
|
|
655
|
+
const filled = Math.min(20, Math.round(percentage / 5));
|
|
656
|
+
const empty = 20 - filled;
|
|
657
|
+
const color = percentage >= 100 ? 'š„' : percentage >= 80 ? 'š§' : 'š©';
|
|
658
|
+
return `[${color.repeat(filled)}${'ā¬'.repeat(empty)}]`;
|
|
659
|
+
}
|
|
660
|
+
//# sourceMappingURL=index.js.map
|