cc-usage-mcp 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/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # cc-usage-mcp
2
+
3
+ Claude Code 用量追踪 MCP 服务器,支持 Windows / macOS / Linux。
4
+
5
+ 解析本地 `~/.claude/projects/` 下的 JSONL 会话文件,统计 token 用量和费用。
6
+
7
+ ## 功能
8
+
9
+ - `usage_summary` - 按项目/模型/日期汇总用量和费用,支持时间范围筛选
10
+ - `usage_today` - 查看今日用量
11
+ - `usage_session` - 查看指定会话或最近会话的用量
12
+ - `usage_cost_estimate` - 估算指定 token 数量的费用
13
+
14
+ ## 安装
15
+
16
+ ```bash
17
+ npm install
18
+ npm run build
19
+ ```
20
+
21
+ ## 配置
22
+
23
+ 在 Claude Code 的 MCP 设置中添加:
24
+
25
+ ### 方式一:项目级配置 (.claude/settings.json)
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "cc-usage": {
31
+ "command": "node",
32
+ "args": ["d:/temp/ikun-cc-usage-mcp/dist/index.js"]
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ ### 方式二:全局配置 (~/.claude/settings.json)
39
+
40
+ ```json
41
+ {
42
+ "mcpServers": {
43
+ "cc-usage": {
44
+ "command": "node",
45
+ "args": ["/path/to/cc-usage-mcp/dist/index.js"]
46
+ }
47
+ }
48
+ }
49
+ ```
50
+
51
+ 路径根据你的系统调整:
52
+ - Windows: `"d:/temp/ikun-cc-usage-mcp/dist/index.js"`
53
+ - macOS/Linux: `"/home/user/cc-usage-mcp/dist/index.js"`
54
+
55
+ ## 支持的模型计价
56
+
57
+ | 模型 | Input | Output | Cache Write | Cache Read |
58
+ |------|-------|--------|-------------|------------|
59
+ | claude-opus-4-6 | $15/M | $75/M | $18.75/M | $1.5/M |
60
+ | claude-sonnet-4-6 | $3/M | $15/M | $3.75/M | $0.3/M |
61
+ | claude-haiku-4-5 | $0.8/M | $4/M | $1/M | $0.08/M |
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
38
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
39
+ const zod_1 = require("zod");
40
+ const parser_1 = require("./parser");
41
+ const server = new mcp_js_1.McpServer({
42
+ name: "cc-usage-mcp",
43
+ version: "1.0.0",
44
+ });
45
+ function formatTokens(n) {
46
+ if (n >= 1000000)
47
+ return `${(n / 1000000).toFixed(2)}M`;
48
+ if (n >= 1000)
49
+ return `${(n / 1000).toFixed(1)}K`;
50
+ return String(n);
51
+ }
52
+ function formatCost(cost) {
53
+ return `$${cost.toFixed(4)}`;
54
+ }
55
+ function formatUsageBlock(usage) {
56
+ return [
57
+ ` Input: ${formatTokens(usage.inputTokens)}`,
58
+ ` Output: ${formatTokens(usage.outputTokens)}`,
59
+ ` Cache Write: ${formatTokens(usage.cacheWriteTokens)}`,
60
+ ` Cache Read: ${formatTokens(usage.cacheReadTokens)}`,
61
+ ].join("\n");
62
+ }
63
+ server.tool("usage_summary", "Get Claude Code usage summary with cost breakdown by project, model, and date", {
64
+ startDate: zod_1.z.string().optional().describe("Start date filter (YYYY-MM-DD)"),
65
+ endDate: zod_1.z.string().optional().describe("End date filter (YYYY-MM-DD)"),
66
+ project: zod_1.z.string().optional().describe("Filter by project name (partial match)"),
67
+ groupBy: zod_1.z
68
+ .enum(["project", "model", "date", "all"])
69
+ .default("all")
70
+ .describe("Group results by category"),
71
+ }, async ({ startDate, endDate, project, groupBy }) => {
72
+ const summary = await (0, parser_1.getUsageSummary)(startDate, endDate, project);
73
+ const lines = [];
74
+ lines.push("=== Claude Code Usage Summary ===");
75
+ lines.push("");
76
+ lines.push(`Total Cost: ${formatCost(summary.totalCost)}`);
77
+ lines.push(`Sessions: ${summary.sessions.length}`);
78
+ lines.push("");
79
+ lines.push("Total Tokens:");
80
+ lines.push(formatUsageBlock(summary.totalUsage));
81
+ if (groupBy === "project" || groupBy === "all") {
82
+ lines.push("");
83
+ lines.push("--- By Project ---");
84
+ const sorted = Object.entries(summary.byProject).sort((a, b) => b[1].cost - a[1].cost);
85
+ for (const [proj, data] of sorted) {
86
+ lines.push(` ${proj}: ${formatCost(data.cost)}`);
87
+ }
88
+ }
89
+ if (groupBy === "model" || groupBy === "all") {
90
+ lines.push("");
91
+ lines.push("--- By Model ---");
92
+ const sorted = Object.entries(summary.byModel).sort((a, b) => b[1].cost - a[1].cost);
93
+ for (const [model, data] of sorted) {
94
+ lines.push(` ${model}: ${formatCost(data.cost)}`);
95
+ }
96
+ }
97
+ if (groupBy === "date" || groupBy === "all") {
98
+ lines.push("");
99
+ lines.push("--- By Date ---");
100
+ const sorted = Object.entries(summary.byDate).sort((a, b) => b[0].localeCompare(a[0]));
101
+ for (const [date, data] of sorted.slice(0, 30)) {
102
+ lines.push(` ${date}: ${formatCost(data.cost)}`);
103
+ }
104
+ }
105
+ return { content: [{ type: "text", text: lines.join("\n") }] };
106
+ });
107
+ server.tool("usage_today", "Get today's Claude Code usage and cost", {}, async () => {
108
+ const today = new Date().toISOString().split("T")[0];
109
+ const summary = await (0, parser_1.getUsageSummary)(today, today);
110
+ const lines = [];
111
+ lines.push(`=== Today's Usage (${today}) ===`);
112
+ lines.push("");
113
+ lines.push(`Cost: ${formatCost(summary.totalCost)}`);
114
+ lines.push(`Sessions: ${summary.sessions.length}`);
115
+ lines.push("");
116
+ lines.push("Tokens:");
117
+ lines.push(formatUsageBlock(summary.totalUsage));
118
+ if (Object.keys(summary.byModel).length > 0) {
119
+ lines.push("");
120
+ lines.push("By Model:");
121
+ for (const [model, data] of Object.entries(summary.byModel)) {
122
+ lines.push(` ${model}: ${formatCost(data.cost)}`);
123
+ }
124
+ }
125
+ return { content: [{ type: "text", text: lines.join("\n") }] };
126
+ });
127
+ server.tool("usage_session", "Get usage for a specific session or the most recent session", {
128
+ sessionId: zod_1.z.string().optional().describe("Session ID (defaults to most recent)"),
129
+ }, async ({ sessionId }) => {
130
+ if (sessionId) {
131
+ const session = await (0, parser_1.getCurrentSessionUsage)(sessionId);
132
+ if (!session) {
133
+ return { content: [{ type: "text", text: "Session not found." }] };
134
+ }
135
+ const lines = [
136
+ `=== Session: ${session.sessionId} ===`,
137
+ "",
138
+ `Project: ${session.project}`,
139
+ `Model: ${session.model}`,
140
+ `Cost: ${formatCost(session.cost)}`,
141
+ `Messages: ${session.messageCount}`,
142
+ `Start: ${session.startTime}`,
143
+ `End: ${session.endTime}`,
144
+ "",
145
+ "Tokens:",
146
+ formatUsageBlock(session.usage),
147
+ ];
148
+ return { content: [{ type: "text", text: lines.join("\n") }] };
149
+ }
150
+ const summary = await (0, parser_1.getUsageSummary)();
151
+ const sorted = summary.sessions.sort((a, b) => {
152
+ if (!a.endTime || !b.endTime)
153
+ return 0;
154
+ return b.endTime.localeCompare(a.endTime);
155
+ });
156
+ if (sorted.length === 0) {
157
+ return { content: [{ type: "text", text: "No sessions found." }] };
158
+ }
159
+ const recent = sorted.slice(0, 5);
160
+ const lines = ["=== Recent Sessions ===", ""];
161
+ for (const s of recent) {
162
+ lines.push(`${s.sessionId.slice(0, 8)}... | ${s.project} | ${s.model} | ${formatCost(s.cost)} | ${s.messageCount} msgs`);
163
+ }
164
+ return { content: [{ type: "text", text: lines.join("\n") }] };
165
+ });
166
+ server.tool("usage_cost_estimate", "Estimate cost for a given number of tokens", {
167
+ model: zod_1.z.string().default("claude-sonnet-4-6").describe("Model name"),
168
+ inputTokens: zod_1.z.number().default(0).describe("Number of input tokens"),
169
+ outputTokens: zod_1.z.number().default(0).describe("Number of output tokens"),
170
+ cacheWriteTokens: zod_1.z.number().default(0).describe("Number of cache write tokens"),
171
+ cacheReadTokens: zod_1.z.number().default(0).describe("Number of cache read tokens"),
172
+ }, async ({ model, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens }) => {
173
+ const { calculateCost, getPricing } = await Promise.resolve().then(() => __importStar(require("./pricing")));
174
+ const pricing = getPricing(model);
175
+ const cost = calculateCost(model, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens);
176
+ const lines = [
177
+ `=== Cost Estimate (${model}) ===`,
178
+ "",
179
+ `Pricing:`,
180
+ ` Input: $${pricing.inputPerMillion}/M tokens`,
181
+ ` Output: $${pricing.outputPerMillion}/M tokens`,
182
+ ` Cache Write: $${pricing.cacheWritePerMillion}/M tokens`,
183
+ ` Cache Read: $${pricing.cacheReadPerMillion}/M tokens`,
184
+ "",
185
+ `Tokens:`,
186
+ ` Input: ${formatTokens(inputTokens)}`,
187
+ ` Output: ${formatTokens(outputTokens)}`,
188
+ ` Cache Write: ${formatTokens(cacheWriteTokens)}`,
189
+ ` Cache Read: ${formatTokens(cacheReadTokens)}`,
190
+ "",
191
+ `Estimated Cost: ${formatCost(cost)}`,
192
+ ];
193
+ return { content: [{ type: "text", text: lines.join("\n") }] };
194
+ });
195
+ async function main() {
196
+ const transport = new stdio_js_1.StdioServerTransport();
197
+ await server.connect(transport);
198
+ }
199
+ main().catch((error) => {
200
+ console.error("Server error:", error);
201
+ process.exit(1);
202
+ });
@@ -0,0 +1,36 @@
1
+ export interface TokenUsage {
2
+ inputTokens: number;
3
+ outputTokens: number;
4
+ cacheWriteTokens: number;
5
+ cacheReadTokens: number;
6
+ }
7
+ export interface SessionSummary {
8
+ sessionId: string;
9
+ project: string;
10
+ model: string;
11
+ usage: TokenUsage;
12
+ cost: number;
13
+ startTime: string;
14
+ endTime: string;
15
+ messageCount: number;
16
+ }
17
+ export interface UsageSummary {
18
+ totalCost: number;
19
+ totalUsage: TokenUsage;
20
+ byProject: Record<string, {
21
+ cost: number;
22
+ usage: TokenUsage;
23
+ }>;
24
+ byModel: Record<string, {
25
+ cost: number;
26
+ usage: TokenUsage;
27
+ }>;
28
+ byDate: Record<string, {
29
+ cost: number;
30
+ usage: TokenUsage;
31
+ }>;
32
+ sessions: SessionSummary[];
33
+ }
34
+ export declare function getUsageSummary(startDate?: string, endDate?: string, projectFilter?: string): Promise<UsageSummary>;
35
+ export declare function getCurrentSessionUsage(sessionId: string): Promise<SessionSummary | null>;
36
+ export declare function getActiveSessionId(): string | null;
package/dist/parser.js ADDED
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getUsageSummary = getUsageSummary;
37
+ exports.getCurrentSessionUsage = getCurrentSessionUsage;
38
+ exports.getActiveSessionId = getActiveSessionId;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const os = __importStar(require("os"));
42
+ const readline = __importStar(require("readline"));
43
+ const pricing_1 = require("./pricing");
44
+ function getClaudeDataDir() {
45
+ const homeDir = os.homedir();
46
+ return path.join(homeDir, ".claude");
47
+ }
48
+ function getProjectsDir() {
49
+ return path.join(getClaudeDataDir(), "projects");
50
+ }
51
+ function decodeProjectDirName(encoded) {
52
+ return encoded.replace(/-/g, path.sep);
53
+ }
54
+ async function parseJsonlFile(filePath) {
55
+ const sessionId = path.basename(filePath, ".jsonl");
56
+ let model = "unknown";
57
+ let totalUsage = {
58
+ inputTokens: 0,
59
+ outputTokens: 0,
60
+ cacheWriteTokens: 0,
61
+ cacheReadTokens: 0,
62
+ };
63
+ let messageCount = 0;
64
+ let startTime = "";
65
+ let endTime = "";
66
+ try {
67
+ const fileStream = fs.createReadStream(filePath, { encoding: "utf-8" });
68
+ const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity });
69
+ for await (const line of rl) {
70
+ if (!line.trim())
71
+ continue;
72
+ try {
73
+ const entry = JSON.parse(line);
74
+ if (entry.type === "summary") {
75
+ if (entry.costUSD !== undefined) {
76
+ const projectDir = path.basename(path.dirname(filePath));
77
+ const project = decodeProjectDirName(projectDir);
78
+ return {
79
+ sessionId,
80
+ project,
81
+ model: entry.model || model,
82
+ usage: {
83
+ inputTokens: entry.inputTokens || 0,
84
+ outputTokens: entry.outputTokens || 0,
85
+ cacheWriteTokens: entry.cacheCreationInputTokens || 0,
86
+ cacheReadTokens: entry.cacheReadInputTokens || 0,
87
+ },
88
+ cost: entry.costUSD,
89
+ startTime: entry.startTime || "",
90
+ endTime: entry.endTime || "",
91
+ messageCount: entry.messageCount || 0,
92
+ };
93
+ }
94
+ }
95
+ if (entry.message?.role === "assistant" && entry.message?.usage) {
96
+ const usage = entry.message.usage;
97
+ totalUsage.inputTokens += usage.input_tokens || 0;
98
+ totalUsage.outputTokens += usage.output_tokens || 0;
99
+ totalUsage.cacheWriteTokens += usage.cache_creation_input_tokens || 0;
100
+ totalUsage.cacheReadTokens += usage.cache_read_input_tokens || 0;
101
+ messageCount++;
102
+ if (entry.message.model) {
103
+ model = entry.message.model;
104
+ }
105
+ }
106
+ if (entry.timestamp) {
107
+ if (!startTime)
108
+ startTime = entry.timestamp;
109
+ endTime = entry.timestamp;
110
+ }
111
+ }
112
+ catch {
113
+ // skip malformed lines
114
+ }
115
+ }
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ if (messageCount === 0)
121
+ return null;
122
+ const projectDir = path.basename(path.dirname(filePath));
123
+ const project = decodeProjectDirName(projectDir);
124
+ const cost = (0, pricing_1.calculateCost)(model, totalUsage.inputTokens, totalUsage.outputTokens, totalUsage.cacheWriteTokens, totalUsage.cacheReadTokens);
125
+ return {
126
+ sessionId,
127
+ project,
128
+ model,
129
+ usage: totalUsage,
130
+ cost,
131
+ startTime,
132
+ endTime,
133
+ messageCount,
134
+ };
135
+ }
136
+ function getAllJsonlFiles(projectsDir) {
137
+ const files = [];
138
+ if (!fs.existsSync(projectsDir))
139
+ return files;
140
+ const projectDirs = fs.readdirSync(projectsDir);
141
+ for (const dir of projectDirs) {
142
+ const dirPath = path.join(projectsDir, dir);
143
+ if (!fs.statSync(dirPath).isDirectory())
144
+ continue;
145
+ const entries = fs.readdirSync(dirPath);
146
+ for (const entry of entries) {
147
+ if (entry.endsWith(".jsonl")) {
148
+ files.push(path.join(dirPath, entry));
149
+ }
150
+ }
151
+ }
152
+ return files;
153
+ }
154
+ async function getUsageSummary(startDate, endDate, projectFilter) {
155
+ const projectsDir = getProjectsDir();
156
+ const files = getAllJsonlFiles(projectsDir);
157
+ const summary = {
158
+ totalCost: 0,
159
+ totalUsage: { inputTokens: 0, outputTokens: 0, cacheWriteTokens: 0, cacheReadTokens: 0 },
160
+ byProject: {},
161
+ byModel: {},
162
+ byDate: {},
163
+ sessions: [],
164
+ };
165
+ for (const file of files) {
166
+ const session = await parseJsonlFile(file);
167
+ if (!session)
168
+ continue;
169
+ if (projectFilter && !session.project.toLowerCase().includes(projectFilter.toLowerCase())) {
170
+ continue;
171
+ }
172
+ const sessionDate = session.startTime ? session.startTime.split("T")[0] : "";
173
+ if (startDate && sessionDate && sessionDate < startDate)
174
+ continue;
175
+ if (endDate && sessionDate && sessionDate > endDate)
176
+ continue;
177
+ summary.totalCost += session.cost;
178
+ summary.totalUsage.inputTokens += session.usage.inputTokens;
179
+ summary.totalUsage.outputTokens += session.usage.outputTokens;
180
+ summary.totalUsage.cacheWriteTokens += session.usage.cacheWriteTokens;
181
+ summary.totalUsage.cacheReadTokens += session.usage.cacheReadTokens;
182
+ if (!summary.byProject[session.project]) {
183
+ summary.byProject[session.project] = {
184
+ cost: 0,
185
+ usage: { inputTokens: 0, outputTokens: 0, cacheWriteTokens: 0, cacheReadTokens: 0 },
186
+ };
187
+ }
188
+ summary.byProject[session.project].cost += session.cost;
189
+ summary.byProject[session.project].usage.inputTokens += session.usage.inputTokens;
190
+ summary.byProject[session.project].usage.outputTokens += session.usage.outputTokens;
191
+ summary.byProject[session.project].usage.cacheWriteTokens += session.usage.cacheWriteTokens;
192
+ summary.byProject[session.project].usage.cacheReadTokens += session.usage.cacheReadTokens;
193
+ if (!summary.byModel[session.model]) {
194
+ summary.byModel[session.model] = {
195
+ cost: 0,
196
+ usage: { inputTokens: 0, outputTokens: 0, cacheWriteTokens: 0, cacheReadTokens: 0 },
197
+ };
198
+ }
199
+ summary.byModel[session.model].cost += session.cost;
200
+ summary.byModel[session.model].usage.inputTokens += session.usage.inputTokens;
201
+ summary.byModel[session.model].usage.outputTokens += session.usage.outputTokens;
202
+ summary.byModel[session.model].usage.cacheWriteTokens += session.usage.cacheWriteTokens;
203
+ summary.byModel[session.model].usage.cacheReadTokens += session.usage.cacheReadTokens;
204
+ if (sessionDate) {
205
+ if (!summary.byDate[sessionDate]) {
206
+ summary.byDate[sessionDate] = {
207
+ cost: 0,
208
+ usage: { inputTokens: 0, outputTokens: 0, cacheWriteTokens: 0, cacheReadTokens: 0 },
209
+ };
210
+ }
211
+ summary.byDate[sessionDate].cost += session.cost;
212
+ summary.byDate[sessionDate].usage.inputTokens += session.usage.inputTokens;
213
+ summary.byDate[sessionDate].usage.outputTokens += session.usage.outputTokens;
214
+ summary.byDate[sessionDate].usage.cacheWriteTokens += session.usage.cacheWriteTokens;
215
+ summary.byDate[sessionDate].usage.cacheReadTokens += session.usage.cacheReadTokens;
216
+ }
217
+ summary.sessions.push(session);
218
+ }
219
+ summary.totalCost = Math.round(summary.totalCost * 1000000) / 1000000;
220
+ return summary;
221
+ }
222
+ async function getCurrentSessionUsage(sessionId) {
223
+ const projectsDir = getProjectsDir();
224
+ const files = getAllJsonlFiles(projectsDir);
225
+ for (const file of files) {
226
+ if (path.basename(file, ".jsonl") === sessionId) {
227
+ return parseJsonlFile(file);
228
+ }
229
+ }
230
+ return null;
231
+ }
232
+ function getActiveSessionId() {
233
+ const claudeDir = getClaudeDataDir();
234
+ const statsFile = path.join(claudeDir, ".stats");
235
+ if (fs.existsSync(statsFile)) {
236
+ try {
237
+ const content = fs.readFileSync(statsFile, "utf-8");
238
+ const data = JSON.parse(content);
239
+ return data.sessionId || null;
240
+ }
241
+ catch {
242
+ return null;
243
+ }
244
+ }
245
+ return null;
246
+ }
@@ -0,0 +1,9 @@
1
+ export interface ModelPricing {
2
+ inputPerMillion: number;
3
+ outputPerMillion: number;
4
+ cacheWritePerMillion: number;
5
+ cacheReadPerMillion: number;
6
+ }
7
+ export declare const MODEL_PRICING: Record<string, ModelPricing>;
8
+ export declare function getPricing(model: string): ModelPricing;
9
+ export declare function calculateCost(model: string, inputTokens: number, outputTokens: number, cacheWriteTokens: number, cacheReadTokens: number): number;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MODEL_PRICING = void 0;
4
+ exports.getPricing = getPricing;
5
+ exports.calculateCost = calculateCost;
6
+ exports.MODEL_PRICING = {
7
+ "claude-opus-4-6": {
8
+ inputPerMillion: 15,
9
+ outputPerMillion: 75,
10
+ cacheWritePerMillion: 18.75,
11
+ cacheReadPerMillion: 1.5,
12
+ },
13
+ "claude-sonnet-4-6": {
14
+ inputPerMillion: 3,
15
+ outputPerMillion: 15,
16
+ cacheWritePerMillion: 3.75,
17
+ cacheReadPerMillion: 0.3,
18
+ },
19
+ "claude-haiku-4-5-20251001": {
20
+ inputPerMillion: 0.8,
21
+ outputPerMillion: 4,
22
+ cacheWritePerMillion: 1,
23
+ cacheReadPerMillion: 0.08,
24
+ },
25
+ "claude-sonnet-4-5-20250514": {
26
+ inputPerMillion: 3,
27
+ outputPerMillion: 15,
28
+ cacheWritePerMillion: 3.75,
29
+ cacheReadPerMillion: 0.3,
30
+ },
31
+ };
32
+ function getPricing(model) {
33
+ if (exports.MODEL_PRICING[model]) {
34
+ return exports.MODEL_PRICING[model];
35
+ }
36
+ for (const key of Object.keys(exports.MODEL_PRICING)) {
37
+ if (model.includes(key) || key.includes(model)) {
38
+ return exports.MODEL_PRICING[key];
39
+ }
40
+ }
41
+ if (model.includes("opus")) {
42
+ return exports.MODEL_PRICING["claude-opus-4-6"];
43
+ }
44
+ if (model.includes("haiku")) {
45
+ return exports.MODEL_PRICING["claude-haiku-4-5-20251001"];
46
+ }
47
+ return exports.MODEL_PRICING["claude-sonnet-4-6"];
48
+ }
49
+ function calculateCost(model, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens) {
50
+ const pricing = getPricing(model);
51
+ const cost = (inputTokens / 1000000) * pricing.inputPerMillion +
52
+ (outputTokens / 1000000) * pricing.outputPerMillion +
53
+ (cacheWriteTokens / 1000000) * pricing.cacheWritePerMillion +
54
+ (cacheReadTokens / 1000000) * pricing.cacheReadPerMillion;
55
+ return Math.round(cost * 1000000) / 1000000;
56
+ }
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "cc-usage-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Claude Code usage tracking MCP server - cross platform (Win/Mac/Linux)",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "cc-usage-mcp": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "start": "node dist/index.js",
15
+ "dev": "ts-node src/index.ts"
16
+ },
17
+ "keywords": ["mcp", "claude-code", "usage", "billing", "token-tracking"],
18
+ "license": "MIT",
19
+ "dependencies": {
20
+ "@modelcontextprotocol/sdk": "^1.12.1"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^20.11.0",
24
+ "typescript": "^5.3.3",
25
+ "ts-node": "^10.9.2"
26
+ }
27
+ }