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,124 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+
4
+ export function registerPrompts(server: McpServer): void {
5
+
6
+ // ============================================================
7
+ // Prompt: Cost analysis
8
+ // ============================================================
9
+ server.registerPrompt(
10
+ 'analyze-costs',
11
+ {
12
+ title: 'Analyze LLM Costs',
13
+ description: 'Analyze your LLM API costs and provide optimization recommendations',
14
+ argsSchema: {
15
+ focus: z.string().optional().describe('Specific area to focus on (e.g., "reduce costs", "compare models", "budget planning")'),
16
+ },
17
+ },
18
+ ({ focus }) => ({
19
+ messages: [
20
+ {
21
+ role: 'user' as const,
22
+ content: {
23
+ type: 'text' as const,
24
+ text: [
25
+ `Please analyze my LLM API costs using the mcp-cost-tracker tools.`,
26
+ ``,
27
+ `Steps:`,
28
+ `1. First, use get_summary to get the overall cost summary`,
29
+ `2. Use get_cost_by_model to see which models cost the most`,
30
+ `3. Use get_cost_by_provider to see provider-level breakdown`,
31
+ `4. Use get_daily_trend to identify spending patterns`,
32
+ `5. Check check_budget for any budget alerts`,
33
+ ``,
34
+ focus ? `Focus area: ${focus}` : '',
35
+ ``,
36
+ `Please provide:`,
37
+ `- A clear summary of total spending`,
38
+ `- The top cost drivers (models and providers)`,
39
+ `- Spending trends over time`,
40
+ `- Specific recommendations to reduce costs`,
41
+ `- Suggestions for more cost-effective model alternatives`,
42
+ ].filter(Boolean).join('\n'),
43
+ },
44
+ },
45
+ ],
46
+ })
47
+ );
48
+
49
+ // ============================================================
50
+ // Prompt: Cost comparison
51
+ // ============================================================
52
+ server.registerPrompt(
53
+ 'compare-options',
54
+ {
55
+ title: 'Compare Model Options',
56
+ description: 'Compare different models for a specific use case to find the most cost-effective option',
57
+ argsSchema: {
58
+ use_case: z.string().describe('Description of the use case (e.g., "code generation", "summarization", "chat")'),
59
+ input_tokens: z.string().optional().describe('Estimated input tokens per request'),
60
+ output_tokens: z.string().optional().describe('Estimated output tokens per request'),
61
+ },
62
+ },
63
+ ({ use_case, input_tokens, output_tokens }) => ({
64
+ messages: [
65
+ {
66
+ role: 'user' as const,
67
+ content: {
68
+ type: 'text' as const,
69
+ text: [
70
+ `I need help choosing the most cost-effective LLM for: ${use_case}`,
71
+ ``,
72
+ input_tokens ? `Estimated input tokens per request: ${input_tokens}` : '',
73
+ output_tokens ? `Estimated output tokens per request: ${output_tokens}` : '',
74
+ ``,
75
+ `Please:`,
76
+ `1. Use lookup_pricing to find relevant models`,
77
+ `2. Use compare_models to compare costs`,
78
+ `3. Use estimate_cost for each option`,
79
+ `4. Consider quality vs cost tradeoffs`,
80
+ `5. Recommend the best option with reasoning`,
81
+ ].filter(Boolean).join('\n'),
82
+ },
83
+ },
84
+ ],
85
+ })
86
+ );
87
+
88
+ // ============================================================
89
+ // Prompt: Budget setup
90
+ // ============================================================
91
+ server.registerPrompt(
92
+ 'setup-budgets',
93
+ {
94
+ title: 'Setup Cost Budgets',
95
+ description: 'Help set up appropriate budgets based on current usage patterns',
96
+ argsSchema: {
97
+ monthly_target: z.string().optional().describe('Target monthly budget in USD'),
98
+ },
99
+ },
100
+ ({ monthly_target }) => ({
101
+ messages: [
102
+ {
103
+ role: 'user' as const,
104
+ content: {
105
+ type: 'text' as const,
106
+ text: [
107
+ `Help me set up cost budgets for my LLM API usage.`,
108
+ ``,
109
+ monthly_target ? `My target monthly budget is: $${monthly_target}` : '',
110
+ ``,
111
+ `Please:`,
112
+ `1. Use get_summary to understand current spending`,
113
+ `2. Use get_cost_by_provider and get_cost_by_model for breakdown`,
114
+ `3. Use get_daily_trend to understand patterns`,
115
+ `4. Recommend appropriate budget limits`,
116
+ `5. Use set_budget to create the budgets`,
117
+ `6. Set up both overall and per-provider budgets if appropriate`,
118
+ ].filter(Boolean).join('\n'),
119
+ },
120
+ },
121
+ ],
122
+ })
123
+ );
124
+ }
@@ -0,0 +1,171 @@
1
+ import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { CostDatabase } from '../storage/database.js';
3
+ import { collectDashboardData, generateTextReport } from '../dashboard/generator.js';
4
+ import { getAllModels, getSupportedProviders } from '../pricing/models.js';
5
+
6
+ export function registerResources(server: McpServer, db: CostDatabase): void {
7
+
8
+ // ============================================================
9
+ // Static Resource: Current cost summary
10
+ // ============================================================
11
+ server.registerResource(
12
+ 'cost-summary',
13
+ 'cost://summary',
14
+ {
15
+ title: 'Cost Summary',
16
+ description: 'Current cost summary across all providers and models',
17
+ mimeType: 'application/json',
18
+ },
19
+ async (uri) => {
20
+ const summary = db.getSummary();
21
+ const byProvider = db.getCostByProvider();
22
+ const byModel = db.getCostByModel();
23
+
24
+ return {
25
+ contents: [{
26
+ uri: uri.href,
27
+ text: JSON.stringify({
28
+ summary,
29
+ by_provider: byProvider,
30
+ by_model: byModel,
31
+ generated_at: new Date().toISOString(),
32
+ }, null, 2),
33
+ }],
34
+ };
35
+ }
36
+ );
37
+
38
+ // ============================================================
39
+ // Static Resource: Supported models pricing
40
+ // ============================================================
41
+ server.registerResource(
42
+ 'pricing-catalog',
43
+ 'cost://pricing',
44
+ {
45
+ title: 'Pricing Catalog',
46
+ description: 'Complete pricing catalog for all supported LLM models',
47
+ mimeType: 'application/json',
48
+ },
49
+ async (uri) => {
50
+ const models = getAllModels();
51
+ const providers = getSupportedProviders();
52
+
53
+ return {
54
+ contents: [{
55
+ uri: uri.href,
56
+ text: JSON.stringify({
57
+ providers,
58
+ total_models: models.length,
59
+ models: models.map(m => ({
60
+ provider: m.provider,
61
+ model: m.model,
62
+ input_price_per_mtok: m.inputPricePerMTok,
63
+ output_price_per_mtok: m.outputPricePerMTok,
64
+ cached_input_price_per_mtok: m.cachedInputPricePerMTok,
65
+ context_window: m.contextWindow,
66
+ category: m.category,
67
+ })),
68
+ }, null, 2),
69
+ }],
70
+ };
71
+ }
72
+ );
73
+
74
+ // ============================================================
75
+ // Static Resource: Budget status
76
+ // ============================================================
77
+ server.registerResource(
78
+ 'budget-status',
79
+ 'cost://budgets',
80
+ {
81
+ title: 'Budget Status',
82
+ description: 'Current status of all configured budgets',
83
+ mimeType: 'application/json',
84
+ },
85
+ async (uri) => {
86
+ const budgets = db.getBudgets();
87
+ const statuses = budgets.map(b => {
88
+ const status = db.checkBudget(b.name);
89
+ return {
90
+ name: b.name,
91
+ period: b.period,
92
+ limit: b.limit_amount,
93
+ spent: status?.spent || 0,
94
+ remaining: status?.remaining || b.limit_amount,
95
+ percentage: status?.percentage || 0,
96
+ exceeded: status?.exceeded || false,
97
+ };
98
+ });
99
+
100
+ return {
101
+ contents: [{
102
+ uri: uri.href,
103
+ text: JSON.stringify({ budgets: statuses }, null, 2),
104
+ }],
105
+ };
106
+ }
107
+ );
108
+
109
+ // ============================================================
110
+ // Static Resource: Full text report
111
+ // ============================================================
112
+ server.registerResource(
113
+ 'full-report',
114
+ 'cost://report',
115
+ {
116
+ title: 'Full Cost Report',
117
+ description: 'Complete cost report in Markdown format',
118
+ mimeType: 'text/markdown',
119
+ },
120
+ async (uri) => {
121
+ const data = collectDashboardData(db, 30);
122
+ const report = generateTextReport(data);
123
+
124
+ return {
125
+ contents: [{
126
+ uri: uri.href,
127
+ text: report,
128
+ }],
129
+ };
130
+ }
131
+ );
132
+
133
+ // ============================================================
134
+ // Dynamic Resource: Provider-specific data
135
+ // ============================================================
136
+ server.registerResource(
137
+ 'provider-cost',
138
+ new ResourceTemplate('cost://provider/{provider}', {
139
+ list: async () => {
140
+ const providers = getSupportedProviders();
141
+ return {
142
+ resources: providers.map(p => ({
143
+ uri: `cost://provider/${p}`,
144
+ name: `${p} costs`,
145
+ })),
146
+ };
147
+ },
148
+ }),
149
+ {
150
+ title: 'Provider Cost Data',
151
+ description: 'Cost data for a specific provider',
152
+ mimeType: 'application/json',
153
+ },
154
+ async (uri, { provider }) => {
155
+ const providerStr = Array.isArray(provider) ? provider[0] : provider;
156
+ const records = db.getUsageRecords({ provider: providerStr, limit: 100 });
157
+ const summary = db.getSummary({ provider: providerStr });
158
+
159
+ return {
160
+ contents: [{
161
+ uri: uri.href,
162
+ text: JSON.stringify({
163
+ provider: providerStr,
164
+ summary,
165
+ recent_records: records.slice(0, 20),
166
+ }, null, 2),
167
+ }],
168
+ };
169
+ }
170
+ );
171
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Format a cost value as USD string.
3
+ */
4
+ export function formatCost(cost: number): string {
5
+ if (cost >= 1) return `$${cost.toFixed(2)}`;
6
+ if (cost >= 0.01) return `$${cost.toFixed(4)}`;
7
+ return `$${cost.toFixed(6)}`;
8
+ }
9
+
10
+ /**
11
+ * Format token count with K/M suffix.
12
+ */
13
+ export function formatTokens(tokens: number): string {
14
+ if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(2)}M`;
15
+ if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}K`;
16
+ return tokens.toString();
17
+ }
18
+
19
+ /**
20
+ * Get ISO date string for start of current period.
21
+ */
22
+ export function getPeriodStart(period: 'daily' | 'weekly' | 'monthly' | 'yearly'): string {
23
+ const now = new Date();
24
+ switch (period) {
25
+ case 'daily':
26
+ return new Date(now.getFullYear(), now.getMonth(), now.getDate()).toISOString();
27
+ case 'weekly': {
28
+ const day = now.getDay();
29
+ const diff = now.getDate() - day + (day === 0 ? -6 : 1);
30
+ return new Date(now.getFullYear(), now.getMonth(), diff).toISOString();
31
+ }
32
+ case 'monthly':
33
+ return new Date(now.getFullYear(), now.getMonth(), 1).toISOString();
34
+ case 'yearly':
35
+ return new Date(now.getFullYear(), 0, 1).toISOString();
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Validate and parse a date string.
41
+ */
42
+ export function parseDate(dateStr: string): Date | null {
43
+ const date = new Date(dateStr);
44
+ return isNaN(date.getTime()) ? null : date;
45
+ }
46
+
47
+ /**
48
+ * Generate a simple unique ID.
49
+ */
50
+ export function generateId(): string {
51
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
52
+ }
53
+
54
+ /**
55
+ * Safely parse JSON with a fallback.
56
+ */
57
+ export function safeJsonParse<T>(json: string, fallback: T): T {
58
+ try {
59
+ return JSON.parse(json);
60
+ } catch {
61
+ return fallback;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Truncate a string to a maximum length.
67
+ */
68
+ export function truncate(str: string, maxLen: number = 100): string {
69
+ if (str.length <= maxLen) return str;
70
+ return str.substring(0, maxLen - 3) + '...';
71
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }