costhawk 1.1.8 → 1.2.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 +132 -107
- package/dist/index.js +330 -0
- package/dist/index.js.map +1 -1
- package/dist/pricing-constants.d.ts +100 -0
- package/dist/pricing-constants.d.ts.map +1 -0
- package/dist/pricing-constants.js +175 -0
- package/dist/pricing-constants.js.map +1 -0
- package/dist/transcript-parser.d.ts +88 -0
- package/dist/transcript-parser.d.ts.map +1 -0
- package/dist/transcript-parser.js +227 -0
- package/dist/transcript-parser.js.map +1 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -1,143 +1,168 @@
|
|
|
1
1
|
# CostHawk MCP Server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official MCP server for [CostHawk](https://costhawk.ai) - AI API cost monitoring and optimization platform.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
> **Early Alpha** - CostHawk is currently in early alpha testing. [Join the waitlist](https://costhawk.ai) to get early access.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Overview
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- Usage summary and cost tracking across providers
|
|
11
|
-
- Savings calculations for flat-rate subscriptions (Claude Max, OpenAI Pro, etc.)
|
|
12
|
-
- Anomaly detection and cost spike alerts
|
|
13
|
-
- Webhook integrations (Slack, Discord, Teams, PagerDuty)
|
|
14
|
-
- Model pricing lookup with context window info
|
|
9
|
+
CostHawk helps teams track, analyze, and optimize their AI API spending across providers like Anthropic, OpenAI, and Google.
|
|
15
10
|
|
|
16
|
-
**
|
|
11
|
+
**Key Features:**
|
|
12
|
+
- Real-time usage tracking and cost analytics
|
|
13
|
+
- **Claude Code local usage tracking** (NEW in v1.2.0)
|
|
14
|
+
- Savings analysis for flat-rate subscriptions (Claude Pro/Max, OpenAI Pro)
|
|
15
|
+
- Budget alerts and anomaly detection
|
|
16
|
+
- Webhook notifications (Slack, Discord, PagerDuty)
|
|
17
|
+
|
|
18
|
+
## Quick Install
|
|
17
19
|
|
|
18
20
|
```bash
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
# Global installation (all projects)
|
|
22
|
+
claude mcp add -s user -e COSTHAWK_API_KEY=YOUR_TOKEN_HERE costhawk -- npx -y costhawk
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
# Project-specific installation
|
|
25
|
+
claude mcp add -e COSTHAWK_API_KEY=YOUR_TOKEN_HERE costhawk -- npx -y costhawk
|
|
26
|
+
```
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
Get your access token from [Settings → Developer](https://costhawk.ai/dashboard/settings) in your CostHawk dashboard (requires approved account).
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
## Available Tools
|
|
27
31
|
|
|
28
|
-
|
|
32
|
+
### Usage Tracking
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
| Tool | Description |
|
|
35
|
+
|------|-------------|
|
|
36
|
+
| `costhawk_get_usage_summary` | Get usage and costs over a time period (by provider/model) |
|
|
37
|
+
| `costhawk_get_usage_by_tag` | Get usage grouped by custom tags (user_id, feature, etc.) |
|
|
38
|
+
| `costhawk_detect_anomalies` | Check for cost anomalies and unusual usage patterns |
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
1. Log into your [CostHawk dashboard](https://costhawk.ai/dashboard)
|
|
34
|
-
2. Go to **Settings → Developer → Create Token**
|
|
35
|
-
3. Copy the token (you'll only see it once)
|
|
40
|
+
### Claude Code Local Tracking (NEW in v1.2.0)
|
|
36
41
|
|
|
37
|
-
|
|
42
|
+
These tools parse your local Claude Code transcripts from `~/.claude/projects/` to track token usage - including the 4 token types Claude Code uses:
|
|
38
43
|
|
|
39
|
-
|
|
44
|
+
- `input_tokens` - Regular input
|
|
45
|
+
- `output_tokens` - Regular output
|
|
46
|
+
- `cache_creation_input_tokens` - Writing to prompt cache
|
|
47
|
+
- `cache_read_input_tokens` - Reading from cache (10x cheaper!)
|
|
40
48
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
| Tool | Description |
|
|
50
|
+
|------|-------------|
|
|
51
|
+
| `costhawk_sync_claude_code_usage` | Sync local usage to CostHawk dashboard for savings analysis |
|
|
52
|
+
| `costhawk_get_local_claude_code_usage` | View local usage offline with cost breakdown |
|
|
53
|
+
| `costhawk_list_claude_code_sessions` | List available Claude Code sessions |
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
claude mcp add -e COSTHAWK_API_KEY=YOUR_TOKEN_HERE costhawk -- npx -y costhawk
|
|
55
|
+
**Example: Check local usage offline**
|
|
47
56
|
```
|
|
48
|
-
|
|
49
|
-
Replace `YOUR_TOKEN_HERE` with your access token from Step 2.
|
|
50
|
-
|
|
51
|
-
**Or configure manually** by adding to `~/.claude/.mcp.json`:
|
|
52
|
-
|
|
53
|
-
```json
|
|
54
|
-
{
|
|
55
|
-
"mcpServers": {
|
|
56
|
-
"costhawk": {
|
|
57
|
-
"command": "npx",
|
|
58
|
-
"args": ["-y", "costhawk"],
|
|
59
|
-
"env": {
|
|
60
|
-
"COSTHAWK_API_KEY": "YOUR_TOKEN_HERE"
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
57
|
+
Use costhawk_get_local_claude_code_usage with subscriptionPlan="max_5x"
|
|
65
58
|
```
|
|
66
59
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
### Step 4: For Claude Desktop (Optional)
|
|
70
|
-
|
|
71
|
-
If using Claude Desktop instead of Claude Code, edit your config file:
|
|
72
|
-
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
73
|
-
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
74
|
-
|
|
75
|
-
```json
|
|
76
|
-
{
|
|
77
|
-
"mcpServers": {
|
|
78
|
-
"costhawk": {
|
|
79
|
-
"command": "npx",
|
|
80
|
-
"args": ["-y", "costhawk"],
|
|
81
|
-
"env": {
|
|
82
|
-
"COSTHAWK_API_KEY": "YOUR_TOKEN_HERE"
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
```
|
|
60
|
+
This shows your token usage, costs at retail rates, and whether you're saving money vs your subscription.
|
|
88
61
|
|
|
89
|
-
|
|
62
|
+
### Savings Analysis
|
|
90
63
|
|
|
91
|
-
|
|
64
|
+
| Tool | Description |
|
|
65
|
+
|------|-------------|
|
|
66
|
+
| `costhawk_get_savings` | Compare retail costs vs subscription costs |
|
|
67
|
+
| `costhawk_get_savings_breakdown` | Per-model breakdown of usage and costs |
|
|
68
|
+
| `costhawk_list_subscriptions` | List your active flat-rate subscriptions |
|
|
69
|
+
|
|
70
|
+
### Pricing & Alerts
|
|
71
|
+
|
|
72
|
+
| Tool | Description |
|
|
73
|
+
|------|-------------|
|
|
74
|
+
| `costhawk_get_model_pricing` | Get current AI model pricing (input/output per 1M tokens) |
|
|
75
|
+
| `costhawk_list_alerts` | List budget warnings, cost spikes, and anomaly alerts |
|
|
76
|
+
|
|
77
|
+
### Webhooks
|
|
92
78
|
|
|
93
79
|
| Tool | Description |
|
|
94
80
|
|------|-------------|
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
|
103
|
-
|
|
104
|
-
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
- "What's my AI API usage this month?"
|
|
111
|
-
- "Show me costs broken down by project tag"
|
|
112
|
-
- "Are there any cost anomalies I should know about?"
|
|
113
|
-
- "What's the current pricing for Claude Opus?"
|
|
114
|
-
- "Create a Slack webhook for budget alerts"
|
|
115
|
-
- "List my unread alerts"
|
|
116
|
-
- "Show my savings vs retail this month"
|
|
117
|
-
- "List my flat-rate subscriptions"
|
|
118
|
-
- "Break down my savings by model"
|
|
81
|
+
| `costhawk_list_webhooks` | List configured webhook endpoints |
|
|
82
|
+
| `costhawk_create_webhook` | Create webhook for Slack, Discord, Teams, PagerDuty |
|
|
83
|
+
|
|
84
|
+
## Claude Code Token Types Explained
|
|
85
|
+
|
|
86
|
+
Claude Code uses caching extensively, which significantly affects your costs:
|
|
87
|
+
|
|
88
|
+
| Token Type | Description | Sonnet 4 Pricing |
|
|
89
|
+
|------------|-------------|------------------|
|
|
90
|
+
| Input | Regular input tokens | $3/1M |
|
|
91
|
+
| Output | Regular output tokens | $15/1M |
|
|
92
|
+
| Cache Write | Writing to prompt cache | $3.75/1M |
|
|
93
|
+
| Cache Read | Reading from cache | $0.30/1M (10x cheaper!) |
|
|
94
|
+
|
|
95
|
+
The cache read savings are significant - CostHawk tracks all 4 types to give you accurate cost calculations.
|
|
119
96
|
|
|
120
97
|
## Environment Variables
|
|
121
98
|
|
|
122
|
-
| Variable | Required |
|
|
123
|
-
|
|
124
|
-
| `COSTHAWK_API_KEY` | Yes |
|
|
125
|
-
| `COSTHAWK_API_URL` | No |
|
|
99
|
+
| Variable | Required | Description |
|
|
100
|
+
|----------|----------|-------------|
|
|
101
|
+
| `COSTHAWK_API_KEY` | Yes* | Your CostHawk API token |
|
|
102
|
+
| `COSTHAWK_API_URL` | No | API base URL (defaults to https://costhawk.ai) |
|
|
103
|
+
|
|
104
|
+
*Required for most tools. Local Claude Code tools work offline without an API key.
|
|
105
|
+
|
|
106
|
+
## Getting Started
|
|
107
|
+
|
|
108
|
+
1. **Get Early Access**: [Join the waitlist](https://costhawk.ai) and wait for approval
|
|
109
|
+
2. **Create API Token**: Go to Settings → Developer → Create Token
|
|
110
|
+
3. **Install MCP Server**: Use the quick install command above
|
|
111
|
+
4. **Start Tracking**: Use the tools in Claude Code or Claude Desktop
|
|
112
|
+
|
|
113
|
+
## Example Workflows
|
|
114
|
+
|
|
115
|
+
### Check if your Claude Max subscription is worth it
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
1. costhawk_list_claude_code_sessions (see what's available)
|
|
119
|
+
2. costhawk_get_local_claude_code_usage with subscriptionPlan="max_5x"
|
|
120
|
+
3. Review the savings breakdown
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Sync usage to dashboard for team visibility
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
1. costhawk_sync_claude_code_usage (uploads to CostHawk)
|
|
127
|
+
2. View detailed analytics at costhawk.ai/dashboard
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Set up cost spike alerts
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
1. costhawk_create_webhook with type="slack" and events=["cost_spike", "budget_alert"]
|
|
134
|
+
2. Get notified when costs exceed thresholds
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Changelog
|
|
138
|
+
|
|
139
|
+
### v1.2.0 (January 2026)
|
|
140
|
+
**Claude Code Local Tracking** - Major new feature release
|
|
141
|
+
|
|
142
|
+
- **New:** `costhawk_sync_claude_code_usage` - Sync local Claude Code transcripts to CostHawk
|
|
143
|
+
- **New:** `costhawk_get_local_claude_code_usage` - View usage offline with cost breakdown
|
|
144
|
+
- **New:** `costhawk_list_claude_code_sessions` - List available local sessions
|
|
145
|
+
- **New:** Full support for all 4 Claude Code token types (input, output, cache write, cache read)
|
|
146
|
+
- **New:** Offline cost calculation with embedded pricing
|
|
147
|
+
- **New:** Savings comparison vs Claude Pro/Max subscriptions
|
|
126
148
|
|
|
127
|
-
|
|
149
|
+
### v1.1.x
|
|
150
|
+
- Savings analysis tools (`costhawk_get_savings`, `costhawk_get_savings_breakdown`)
|
|
151
|
+
- Subscription management (`costhawk_list_subscriptions`)
|
|
152
|
+
- Webhook support for Slack, Discord, Teams, PagerDuty
|
|
153
|
+
- Usage tracking and anomaly detection
|
|
128
154
|
|
|
129
|
-
|
|
130
|
-
-
|
|
131
|
-
-
|
|
155
|
+
### v1.0.x
|
|
156
|
+
- Initial release
|
|
157
|
+
- Basic usage summary and cost tracking
|
|
158
|
+
- Model pricing lookup
|
|
159
|
+
- Alert notifications
|
|
132
160
|
|
|
133
|
-
|
|
134
|
-
- Verify your access token is correct
|
|
135
|
-
- Check the token is active in CostHawk dashboard (Settings → Developer)
|
|
136
|
-
- Make sure you've been approved from the waitlist
|
|
161
|
+
## Links
|
|
137
162
|
|
|
138
|
-
|
|
139
|
-
-
|
|
140
|
-
-
|
|
163
|
+
- [Join Waitlist](https://costhawk.ai)
|
|
164
|
+
- [Documentation](https://docs.costhawk.ai)
|
|
165
|
+
- [npm Package](https://www.npmjs.com/package/costhawk)
|
|
141
166
|
|
|
142
167
|
## License
|
|
143
168
|
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
// Claude Code local transcript parsing
|
|
6
|
+
import { claudeCodeDirectoryExists, discoverTranscripts, parseAllTranscripts, aggregateUsage, } from "./transcript-parser.js";
|
|
7
|
+
import { calculateCost, formatCost, formatTokens, calculateSavings, CLAUDE_SUBSCRIPTIONS, } from "./pricing-constants.js";
|
|
5
8
|
// Constants
|
|
6
9
|
const CHARACTER_LIMIT = 25000;
|
|
7
10
|
const REQUEST_TIMEOUT_MS = 30000; // 30 seconds
|
|
@@ -296,6 +299,75 @@ function formatSavingsBreakdownMarkdown(data) {
|
|
|
296
299
|
}
|
|
297
300
|
return truncateResponse(output);
|
|
298
301
|
}
|
|
302
|
+
// Claude Code local usage formatters
|
|
303
|
+
function formatClaudeCodeLocalUsageMarkdown(data) {
|
|
304
|
+
let output = `# Claude Code Local Usage\n\n`;
|
|
305
|
+
output += `## Summary\n`;
|
|
306
|
+
output += `| Metric | Value |\n|--------|-------|\n`;
|
|
307
|
+
output += `| Sessions | ${data.sessions} |\n`;
|
|
308
|
+
output += `| Total Tokens | ${formatTokens(data.tokens.total)} |\n`;
|
|
309
|
+
output += `| Input Tokens | ${formatTokens(data.tokens.input)} |\n`;
|
|
310
|
+
output += `| Output Tokens | ${formatTokens(data.tokens.output)} |\n`;
|
|
311
|
+
output += `| Cache Write Tokens | ${formatTokens(data.tokens.cacheCreation)} |\n`;
|
|
312
|
+
output += `| Cache Read Tokens | ${formatTokens(data.tokens.cacheRead)} |\n\n`;
|
|
313
|
+
output += `## Cost Breakdown (Retail Rates)\n`;
|
|
314
|
+
output += `| Category | Cost |\n|----------|------|\n`;
|
|
315
|
+
output += `| Input | ${formatCost(data.cost.inputCost)} |\n`;
|
|
316
|
+
output += `| Output | ${formatCost(data.cost.outputCost)} |\n`;
|
|
317
|
+
output += `| Cache Write | ${formatCost(data.cost.cacheWriteCost)} |\n`;
|
|
318
|
+
output += `| Cache Read | ${formatCost(data.cost.cacheReadCost)} |\n`;
|
|
319
|
+
output += `| **Total** | **${formatCost(data.cost.totalCost)}** |\n\n`;
|
|
320
|
+
if (data.savings) {
|
|
321
|
+
const statusEmoji = data.savings.status === "saving" ? "✅" : data.savings.status === "losing" ? "⚠️" : "➖";
|
|
322
|
+
output += `## Subscription Savings\n`;
|
|
323
|
+
output += `| Metric | Value |\n|--------|-------|\n`;
|
|
324
|
+
output += `| Subscription | ${data.savings.subscriptionPlan} |\n`;
|
|
325
|
+
output += `| Monthly Cost | ${formatCost(data.savings.subscriptionCost)} |\n`;
|
|
326
|
+
output += `| Retail Value | ${formatCost(data.cost.totalCost)} |\n`;
|
|
327
|
+
output += `| Savings | ${formatCost(data.savings.savingsAmount)} |\n`;
|
|
328
|
+
output += `| Savings Rate | ${data.savings.savingsPercentage.toFixed(1)}% |\n`;
|
|
329
|
+
output += `| Status | ${statusEmoji} ${data.savings.status} |\n\n`;
|
|
330
|
+
}
|
|
331
|
+
if (data.sessionDetails.length > 0) {
|
|
332
|
+
output += `## Recent Sessions\n`;
|
|
333
|
+
output += `| Session | Model | Tokens | Cost |\n|---------|-------|--------|------|\n`;
|
|
334
|
+
for (const session of data.sessionDetails.slice(0, 10)) {
|
|
335
|
+
const shortId = session.sessionId.slice(0, 8);
|
|
336
|
+
output += `| ${shortId}... | ${session.model} | ${formatTokens(session.tokens)} | ${formatCost(session.cost)} |\n`;
|
|
337
|
+
}
|
|
338
|
+
if (data.sessionDetails.length > 10) {
|
|
339
|
+
output += `\n*...and ${data.sessionDetails.length - 10} more sessions*\n`;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return truncateResponse(output);
|
|
343
|
+
}
|
|
344
|
+
function formatClaudeCodeSyncResultMarkdown(data) {
|
|
345
|
+
const statusEmoji = data.success ? "✅" : "❌";
|
|
346
|
+
let output = `# Claude Code Sync Result\n\n`;
|
|
347
|
+
output += `${statusEmoji} ${data.message}\n\n`;
|
|
348
|
+
output += `| Metric | Value |\n|--------|-------|\n`;
|
|
349
|
+
output += `| New Sessions | ${data.sessionsNew} |\n`;
|
|
350
|
+
output += `| Updated Sessions | ${data.sessionsUpdated} |\n`;
|
|
351
|
+
output += `| Total Tokens | ${formatTokens(data.totalTokens)} |\n`;
|
|
352
|
+
output += `| Estimated Cost | ${formatCost(data.estimatedCost)} |\n`;
|
|
353
|
+
return truncateResponse(output);
|
|
354
|
+
}
|
|
355
|
+
function formatClaudeCodeSessionsMarkdown(sessions) {
|
|
356
|
+
if (sessions.length === 0) {
|
|
357
|
+
return "# Claude Code Sessions\n\nNo sessions found. Make sure you have used Claude Code recently.";
|
|
358
|
+
}
|
|
359
|
+
let output = `# Claude Code Sessions\n\n`;
|
|
360
|
+
output += `Found ${sessions.length} session(s)\n\n`;
|
|
361
|
+
output += `| Session ID | Project | Last Modified | Size |\n|------------|---------|---------------|------|\n`;
|
|
362
|
+
for (const session of sessions) {
|
|
363
|
+
const shortId = session.sessionId.slice(0, 12);
|
|
364
|
+
const shortProject = session.projectHash.slice(0, 8);
|
|
365
|
+
const date = session.lastModified.toISOString().split("T")[0];
|
|
366
|
+
const sizeKB = (session.size / 1024).toFixed(1);
|
|
367
|
+
output += `| ${shortId}... | ${shortProject}... | ${date} | ${sizeKB} KB |\n`;
|
|
368
|
+
}
|
|
369
|
+
return truncateResponse(output);
|
|
370
|
+
}
|
|
299
371
|
// Create MCP server
|
|
300
372
|
const server = new McpServer({
|
|
301
373
|
name: "costhawk-mcp-server",
|
|
@@ -444,6 +516,46 @@ const GetSavingsBreakdownSchema = {
|
|
|
444
516
|
orgId: z.string().optional().describe("Optional organization ID to scope the breakdown"),
|
|
445
517
|
format: z.enum(["markdown", "json"]).optional().default("markdown").describe("Response format"),
|
|
446
518
|
};
|
|
519
|
+
// Claude Code local transcript schemas
|
|
520
|
+
const SyncClaudeCodeUsageSchema = {
|
|
521
|
+
apiKey: z
|
|
522
|
+
.string()
|
|
523
|
+
.optional()
|
|
524
|
+
.describe("Your CostHawk API key. If not provided, uses COSTHAWK_API_KEY environment variable."),
|
|
525
|
+
maxAgeHours: z
|
|
526
|
+
.number()
|
|
527
|
+
.min(1)
|
|
528
|
+
.max(720)
|
|
529
|
+
.optional()
|
|
530
|
+
.default(24)
|
|
531
|
+
.describe("Only sync sessions modified within this many hours (1-720, default 24)"),
|
|
532
|
+
format: z.enum(["markdown", "json"]).optional().default("markdown").describe("Response format"),
|
|
533
|
+
};
|
|
534
|
+
const GetLocalClaudeCodeUsageSchema = {
|
|
535
|
+
maxAgeHours: z
|
|
536
|
+
.number()
|
|
537
|
+
.min(1)
|
|
538
|
+
.max(720)
|
|
539
|
+
.optional()
|
|
540
|
+
.default(24)
|
|
541
|
+
.describe("Only include sessions modified within this many hours (1-720, default 24)"),
|
|
542
|
+
subscriptionPlan: z
|
|
543
|
+
.enum(["pro", "max_5x", "max_20x"])
|
|
544
|
+
.optional()
|
|
545
|
+
.describe("Your Claude subscription plan for savings calculation"),
|
|
546
|
+
format: z.enum(["markdown", "json"]).optional().default("markdown").describe("Response format"),
|
|
547
|
+
};
|
|
548
|
+
const ListClaudeCodeSessionsSchema = {
|
|
549
|
+
maxAgeHours: z
|
|
550
|
+
.number()
|
|
551
|
+
.min(1)
|
|
552
|
+
.max(720)
|
|
553
|
+
.optional()
|
|
554
|
+
.default(168)
|
|
555
|
+
.describe("Only include sessions modified within this many hours (1-720, default 168 = 7 days)"),
|
|
556
|
+
limit: z.number().min(1).max(100).optional().default(20).describe("Maximum number of sessions to show"),
|
|
557
|
+
format: z.enum(["markdown", "json"]).optional().default("markdown").describe("Response format"),
|
|
558
|
+
};
|
|
447
559
|
// Register tools using registerTool (recommended API)
|
|
448
560
|
server.registerTool("costhawk_get_usage_summary", {
|
|
449
561
|
description: `Get a summary of your AI API usage and costs over a time period. Returns total costs, API calls, and token usage broken down by provider and model. Supports preset periods (last_24h, today, yesterday, last_7d, last_30d) or custom date ranges.`,
|
|
@@ -832,6 +944,224 @@ server.registerTool("costhawk_list_alerts", {
|
|
|
832
944
|
};
|
|
833
945
|
}
|
|
834
946
|
});
|
|
947
|
+
// ============================================================================
|
|
948
|
+
// CLAUDE CODE LOCAL TRANSCRIPT TOOLS
|
|
949
|
+
// ============================================================================
|
|
950
|
+
// These tools parse local Claude Code transcripts from ~/.claude/projects/
|
|
951
|
+
// to track token usage and calculate savings vs subscription costs.
|
|
952
|
+
server.registerTool("costhawk_sync_claude_code_usage", {
|
|
953
|
+
description: `Sync your local Claude Code usage to CostHawk. Parses transcript files from ~/.claude/projects/ and uploads token counts to your CostHawk dashboard for savings analysis. Requires API key.`,
|
|
954
|
+
inputSchema: SyncClaudeCodeUsageSchema,
|
|
955
|
+
annotations: WRITE_ANNOTATIONS,
|
|
956
|
+
}, async (args, _extra) => {
|
|
957
|
+
const apiKey = getApiKey(args.apiKey);
|
|
958
|
+
if (!apiKey) {
|
|
959
|
+
return {
|
|
960
|
+
content: [
|
|
961
|
+
{
|
|
962
|
+
type: "text",
|
|
963
|
+
text: "Error: No API key provided. You must first sign up at https://costhawk.ai to get an API key. Once approved, go to Settings > Developer > Create Token, then set COSTHAWK_API_KEY in your MCP configuration.",
|
|
964
|
+
},
|
|
965
|
+
],
|
|
966
|
+
isError: true,
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
// Check if Claude Code directory exists
|
|
970
|
+
if (!claudeCodeDirectoryExists()) {
|
|
971
|
+
return {
|
|
972
|
+
content: [
|
|
973
|
+
{
|
|
974
|
+
type: "text",
|
|
975
|
+
text: "Error: Claude Code directory not found at ~/.claude/projects/. Make sure you have used Claude Code at least once.",
|
|
976
|
+
},
|
|
977
|
+
],
|
|
978
|
+
isError: true,
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
try {
|
|
982
|
+
// Parse all transcripts within age limit
|
|
983
|
+
const maxAgeHours = args.maxAgeHours ?? 24;
|
|
984
|
+
const sessions = parseAllTranscripts(maxAgeHours);
|
|
985
|
+
if (sessions.length === 0) {
|
|
986
|
+
return {
|
|
987
|
+
content: [
|
|
988
|
+
{
|
|
989
|
+
type: "text",
|
|
990
|
+
text: `No Claude Code sessions found within the last ${maxAgeHours} hours. Try increasing maxAgeHours or use Claude Code first.`,
|
|
991
|
+
},
|
|
992
|
+
],
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
// Transform sessions to API payload format
|
|
996
|
+
const payload = {
|
|
997
|
+
sessions: sessions.map((session) => ({
|
|
998
|
+
sessionId: session.sessionId,
|
|
999
|
+
projectHash: session.projectHash,
|
|
1000
|
+
model: session.model,
|
|
1001
|
+
inputTokens: session.tokens.inputTokens,
|
|
1002
|
+
outputTokens: session.tokens.outputTokens,
|
|
1003
|
+
cacheCreationTokens: session.tokens.cacheCreationTokens,
|
|
1004
|
+
cacheReadTokens: session.tokens.cacheReadTokens,
|
|
1005
|
+
messageCount: session.messageCount,
|
|
1006
|
+
startTime: session.startTime,
|
|
1007
|
+
endTime: session.endTime,
|
|
1008
|
+
})),
|
|
1009
|
+
clientVersion: "1.2.0",
|
|
1010
|
+
syncedAt: new Date().toISOString(),
|
|
1011
|
+
};
|
|
1012
|
+
// Send to CostHawk API
|
|
1013
|
+
const result = await apiRequest("/api/mcp/usage/claude-code", {
|
|
1014
|
+
method: "POST",
|
|
1015
|
+
apiKey,
|
|
1016
|
+
body: payload,
|
|
1017
|
+
});
|
|
1018
|
+
const text = args.format === "json" ? JSON.stringify(result, null, 2) : formatClaudeCodeSyncResultMarkdown(result);
|
|
1019
|
+
return {
|
|
1020
|
+
content: [{ type: "text", text }],
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
catch (error) {
|
|
1024
|
+
return {
|
|
1025
|
+
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : "Unknown error"}` }],
|
|
1026
|
+
isError: true,
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
server.registerTool("costhawk_get_local_claude_code_usage", {
|
|
1031
|
+
description: `Get Claude Code usage from local transcripts WITHOUT uploading to CostHawk. Works offline. Shows token counts, costs at retail rates, and optional savings calculation if you provide your subscription plan.`,
|
|
1032
|
+
inputSchema: GetLocalClaudeCodeUsageSchema,
|
|
1033
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
1034
|
+
}, async (args, _extra) => {
|
|
1035
|
+
// Check if Claude Code directory exists
|
|
1036
|
+
if (!claudeCodeDirectoryExists()) {
|
|
1037
|
+
return {
|
|
1038
|
+
content: [
|
|
1039
|
+
{
|
|
1040
|
+
type: "text",
|
|
1041
|
+
text: "Error: Claude Code directory not found at ~/.claude/projects/. Make sure you have used Claude Code at least once.",
|
|
1042
|
+
},
|
|
1043
|
+
],
|
|
1044
|
+
isError: true,
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
try {
|
|
1048
|
+
const maxAgeHours = args.maxAgeHours ?? 24;
|
|
1049
|
+
const sessions = parseAllTranscripts(maxAgeHours);
|
|
1050
|
+
if (sessions.length === 0) {
|
|
1051
|
+
return {
|
|
1052
|
+
content: [
|
|
1053
|
+
{
|
|
1054
|
+
type: "text",
|
|
1055
|
+
text: `No Claude Code sessions found within the last ${maxAgeHours} hours. Try increasing maxAgeHours or use Claude Code first.`,
|
|
1056
|
+
},
|
|
1057
|
+
],
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
// Aggregate usage
|
|
1061
|
+
const aggregated = aggregateUsage(sessions);
|
|
1062
|
+
// Calculate total cost using dominant model pricing
|
|
1063
|
+
const dominantModel = Object.entries(aggregated.models).sort((a, b) => b[1] - a[1])[0]?.[0] || "unknown";
|
|
1064
|
+
const cost = calculateCost({
|
|
1065
|
+
inputTokens: aggregated.tokens.inputTokens,
|
|
1066
|
+
outputTokens: aggregated.tokens.outputTokens,
|
|
1067
|
+
cacheCreationTokens: aggregated.tokens.cacheCreationTokens,
|
|
1068
|
+
cacheReadTokens: aggregated.tokens.cacheReadTokens,
|
|
1069
|
+
}, dominantModel);
|
|
1070
|
+
// Build session details
|
|
1071
|
+
const sessionDetails = sessions.map((session) => {
|
|
1072
|
+
const sessionCost = calculateCost(session.tokens, session.model);
|
|
1073
|
+
const totalTokens = session.tokens.inputTokens +
|
|
1074
|
+
session.tokens.outputTokens +
|
|
1075
|
+
session.tokens.cacheCreationTokens +
|
|
1076
|
+
session.tokens.cacheReadTokens;
|
|
1077
|
+
return {
|
|
1078
|
+
sessionId: session.sessionId,
|
|
1079
|
+
projectHash: session.projectHash,
|
|
1080
|
+
model: session.model,
|
|
1081
|
+
tokens: totalTokens,
|
|
1082
|
+
cost: sessionCost.totalCost,
|
|
1083
|
+
startTime: session.startTime,
|
|
1084
|
+
endTime: session.endTime,
|
|
1085
|
+
};
|
|
1086
|
+
});
|
|
1087
|
+
// Build response
|
|
1088
|
+
const response = {
|
|
1089
|
+
sessions: aggregated.totalSessions,
|
|
1090
|
+
tokens: {
|
|
1091
|
+
input: aggregated.tokens.inputTokens,
|
|
1092
|
+
output: aggregated.tokens.outputTokens,
|
|
1093
|
+
cacheCreation: aggregated.tokens.cacheCreationTokens,
|
|
1094
|
+
cacheRead: aggregated.tokens.cacheReadTokens,
|
|
1095
|
+
total: aggregated.totalTokens,
|
|
1096
|
+
},
|
|
1097
|
+
cost,
|
|
1098
|
+
sessionDetails,
|
|
1099
|
+
};
|
|
1100
|
+
// Add savings calculation if subscription plan provided
|
|
1101
|
+
if (args.subscriptionPlan) {
|
|
1102
|
+
const subscription = CLAUDE_SUBSCRIPTIONS[args.subscriptionPlan];
|
|
1103
|
+
const savings = calculateSavings(cost.totalCost, subscription.monthlyFee);
|
|
1104
|
+
response.savings = {
|
|
1105
|
+
subscriptionPlan: subscription.name,
|
|
1106
|
+
subscriptionCost: subscription.monthlyFee,
|
|
1107
|
+
savingsAmount: savings.savings,
|
|
1108
|
+
savingsPercentage: savings.savingsPercentage,
|
|
1109
|
+
status: savings.status,
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
const text = args.format === "json" ? JSON.stringify(response, null, 2) : formatClaudeCodeLocalUsageMarkdown(response);
|
|
1113
|
+
return {
|
|
1114
|
+
content: [{ type: "text", text }],
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
catch (error) {
|
|
1118
|
+
return {
|
|
1119
|
+
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : "Unknown error"}` }],
|
|
1120
|
+
isError: true,
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
server.registerTool("costhawk_list_claude_code_sessions", {
|
|
1125
|
+
description: `List available Claude Code sessions from local transcripts. Shows session IDs, project hashes, modification dates, and file sizes. Use this to see what sessions are available before syncing.`,
|
|
1126
|
+
inputSchema: ListClaudeCodeSessionsSchema,
|
|
1127
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
1128
|
+
}, async (args, _extra) => {
|
|
1129
|
+
// Check if Claude Code directory exists
|
|
1130
|
+
if (!claudeCodeDirectoryExists()) {
|
|
1131
|
+
return {
|
|
1132
|
+
content: [
|
|
1133
|
+
{
|
|
1134
|
+
type: "text",
|
|
1135
|
+
text: "Error: Claude Code directory not found at ~/.claude/projects/. Make sure you have used Claude Code at least once.",
|
|
1136
|
+
},
|
|
1137
|
+
],
|
|
1138
|
+
isError: true,
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
try {
|
|
1142
|
+
const maxAgeHours = args.maxAgeHours ?? 168;
|
|
1143
|
+
const limit = args.limit ?? 20;
|
|
1144
|
+
const transcripts = discoverTranscripts(maxAgeHours).slice(0, limit);
|
|
1145
|
+
const text = args.format === "json"
|
|
1146
|
+
? JSON.stringify(transcripts.map((t) => ({
|
|
1147
|
+
sessionId: t.sessionId,
|
|
1148
|
+
projectHash: t.projectHash,
|
|
1149
|
+
lastModified: t.lastModified.toISOString(),
|
|
1150
|
+
size: t.size,
|
|
1151
|
+
path: t.path,
|
|
1152
|
+
})), null, 2)
|
|
1153
|
+
: formatClaudeCodeSessionsMarkdown(transcripts);
|
|
1154
|
+
return {
|
|
1155
|
+
content: [{ type: "text", text }],
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
catch (error) {
|
|
1159
|
+
return {
|
|
1160
|
+
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : "Unknown error"}` }],
|
|
1161
|
+
isError: true,
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
});
|
|
835
1165
|
// Start server
|
|
836
1166
|
async function main() {
|
|
837
1167
|
const transport = new StdioServerTransport();
|