@ccusage/mcp 0.0.1 → 17.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ryoppippi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,15 +1,82 @@
1
- # mk
1
+ <div align="center">
2
+ <img src="https://cdn.jsdelivr.net/gh/ryoppippi/ccusage@main/docs/public/logo.svg" alt="ccusage logo" width="256" height="256">
3
+ <h1>@ccusage/mcp</h1>
4
+ </div>
2
5
 
3
- To install dependencies:
6
+ <p align="center">
7
+ <a href="https://socket.dev/api/npm/package/@ccusage/mcp"><img src="https://socket.dev/api/badge/npm/package/@ccusage/mcp" alt="Socket Badge" /></a>
8
+ <a href="https://npmjs.com/package/@ccusage/mcp"><img src="https://img.shields.io/npm/v/@ccusage/mcp?color=yellow" alt="npm version" /></a>
9
+ <a href="https://tanstack.com/stats/npm?packageGroups=%5B%7B%22packages%22:%5B%7B%22name%22:%22@ccusage/mcp%22%7D%5D%7D%5D&range=30-days&transform=none&binType=daily&showDataMode=all&height=400"><img src="https://img.shields.io/npm/dy/@ccusage/mcp" alt="NPM Downloads" /></a>
10
+ <a href="https://packagephobia.com/result?p=@ccusage/mcp"><img src="https://packagephobia.com/badge?p=@ccusage/mcp" alt="install size" /></a>
11
+ <a href="https://deepwiki.com/ryoppippi/ccusage"><img src="https://img.shields.io/badge/DeepWiki-ryoppippi%2Fccusage-blue.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAyCAYAAAAnWDnqAAAAAXNSR0IArs4c6QAAA05JREFUaEPtmUtyEzEQhtWTQyQLHNak2AB7ZnyXZMEjXCGeK/AIi+QuHrMnbChYY7MIh8g01fJoopFb0uhhEqqcbWTp06/uv1saEDv4O3n3dV60RfP947Mm9/SQc0ICFQgzfc4CYZoTPAswgSJCCUJUnAAoRHOAUOcATwbmVLWdGoH//PB8mnKqScAhsD0kYP3j/Yt5LPQe2KvcXmGvRHcDnpxfL2zOYJ1mFwrryWTz0advv1Ut4CJgf5uhDuDj5eUcAUoahrdY/56ebRWeraTjMt/00Sh3UDtjgHtQNHwcRGOC98BJEAEymycmYcWwOprTgcB6VZ5JK5TAJ+fXGLBm3FDAmn6oPPjR4rKCAoJCal2eAiQp2x0vxTPB3ALO2CRkwmDy5WohzBDwSEFKRwPbknEggCPB/imwrycgxX2NzoMCHhPkDwqYMr9tRcP5qNrMZHkVnOjRMWwLCcr8ohBVb1OMjxLwGCvjTikrsBOiA6fNyCrm8V1rP93iVPpwaE+gO0SsWmPiXB+jikdf6SizrT5qKasx5j8ABbHpFTx+vFXp9EnYQmLx02h1QTTrl6eDqxLnGjporxl3NL3agEvXdT0WmEost648sQOYAeJS9Q7bfUVoMGnjo4AZdUMQku50McDcMWcBPvr0SzbTAFDfvJqwLzgxwATnCgnp4wDl6Aa+Ax283gghmj+vj7feE2KBBRMW3FzOpLOADl0Isb5587h/U4gGvkt5v60Z1VLG8BhYjbzRwyQZemwAd6cCR5/XFWLYZRIMpX39AR0tjaGGiGzLVyhse5C9RKC6ai42ppWPKiBagOvaYk8lO7DajerabOZP46Lby5wKjw1HCRx7p9sVMOWGzb/vA1hwiWc6jm3MvQDTogQkiqIhJV0nBQBTU+3okKCFDy9WwferkHjtxib7t3xIUQtHxnIwtx4mpg26/HfwVNVDb4oI9RHmx5WGelRVlrtiw43zboCLaxv46AZeB3IlTkwouebTr1y2NjSpHz68WNFjHvupy3q8TFn3Hos2IAk4Ju5dCo8B3wP7VPr/FGaKiG+T+v+TQqIrOqMTL1VdWV1DdmcbO8KXBz6esmYWYKPwDL5b5FA1a0hwapHiom0r/cKaoqr+27/XcrS5UwSMbQAAAABJRU5ErkJggg==" alt="DeepWiki"></a>
12
+ <!-- DeepWiki badge generated by https://deepwiki.ryoppippi.com/ -->
13
+ </p>
14
+
15
+ <div align="center">
16
+ <img src="https://cdn.jsdelivr.net/gh/ryoppippi/ccusage@main/docs/public/mcp-claude-desktop.avif" alt="Claude Desktop MCP integration screenshot" width="640">
17
+ </div>
18
+
19
+ > MCP (Model Context Protocol) server implementation for ccusage - provides Claude Code usage data through the MCP protocol.
20
+
21
+ ## Quick Start
4
22
 
5
23
  ```bash
6
- bun install
24
+ # Using bunx (recommended for speed)
25
+ bunx @ccusage/mcp@latest
26
+
27
+ # Using npx
28
+ npx @ccusage/mcp@latest
29
+
30
+ # Start with HTTP transport
31
+ bunx @ccusage/mcp@latest -- --type http --port 8080
7
32
  ```
8
33
 
9
- To run:
34
+ ## Integrations
10
35
 
11
- ```bash
12
- bun run index.ts
36
+ ### Claude Desktop Integration
37
+
38
+ Add to your Claude Desktop MCP configuration:
39
+
40
+ ```json
41
+ {
42
+ "mcpServers": {
43
+ "ccusage": {
44
+ "command": "npx",
45
+ "args": ["@ccusage/mcp@latest"],
46
+ "type": "stdio"
47
+ }
48
+ }
49
+ }
13
50
  ```
14
51
 
15
- This project was created using `bun init` in bun v1.2.20. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
52
+ ### Claude Code
53
+
54
+ ```sh
55
+ claude mcp add ccusage npx -- @ccusage/mcp@latest
56
+ ```
57
+
58
+ ## Documentation
59
+
60
+ For full documentation, visit **[ccusage.com/guide/mcp-server](https://ccusage.com/guide/mcp-server)**
61
+
62
+ ## Sponsors
63
+
64
+ ### Featured Sponsor
65
+
66
+ Check out [ccusage: The Claude Code cost scorecard that went viral](https://www.youtube.com/watch?v=Ak6qpQ5qdgk)
67
+
68
+ <p align="center">
69
+ <a href="https://www.youtube.com/watch?v=Ak6qpQ5qdgk">
70
+ <img src="https://cdn.jsdelivr.net/gh/ryoppippi/ccusage@main/docs/public/ccusage_thumbnail.png" alt="ccusage: The Claude Code cost scorecard that went viral" width="600">
71
+ </a>
72
+ </p>
73
+
74
+ <p align="center">
75
+ <a href="https://github.com/sponsors/ryoppippi">
76
+ <img src="https://cdn.jsdelivr.net/gh/ryoppippi/sponsors@main/sponsors.svg">
77
+ </a>
78
+ </p>
79
+
80
+ ## License
81
+
82
+ MIT © [@ryoppippi](https://github.com/ryoppippi)
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,539 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ import process$1 from "node:process";
4
+ import { serve } from "@hono/node-server";
5
+ import { getClaudePaths } from "ccusage/data-loader";
6
+ import { logger } from "ccusage/logger";
7
+ import { cli, define } from "gunshi";
8
+ import { StreamableHTTPTransport } from "@hono/mcp";
9
+ import "@modelcontextprotocol/sdk/client/index.js";
10
+ import "@modelcontextprotocol/sdk/inMemory.js";
11
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
+ import a from "node:fs/promises";
14
+ import c from "node:path";
15
+ import b from "node:fs";
16
+ import F from "node:os";
17
+ import { Hono } from "hono/tiny";
18
+ import { z } from "zod";
19
+ import spawn, { SubprocessError } from "nano-spawn";
20
+ var name = "@ccusage/mcp";
21
+ var version = "17.0.0";
22
+ var description = "MCP server implementation for ccusage data";
23
+ var d = Object.defineProperty;
24
+ var n = (s, t) => d(s, "name", {
25
+ value: t,
26
+ configurable: !0
27
+ });
28
+ typeof Symbol.asyncDispose != "symbol" && Object.defineProperty(Symbol, "asyncDispose", {
29
+ configurable: !1,
30
+ enumerable: !1,
31
+ writable: !1,
32
+ value: Symbol.for("asyncDispose")
33
+ });
34
+ var P = class {
35
+ static {
36
+ n(this, "FsFixture");
37
+ }
38
+ path;
39
+ constructor(t) {
40
+ this.path = t;
41
+ }
42
+ getPath(...t) {
43
+ return c.join(this.path, ...t);
44
+ }
45
+ exists(t = "") {
46
+ return a.access(this.getPath(t)).then(() => !0, () => !1);
47
+ }
48
+ rm(t = "") {
49
+ return a.rm(this.getPath(t), {
50
+ recursive: !0,
51
+ force: !0
52
+ });
53
+ }
54
+ cp(t, r, i) {
55
+ return r ? r.endsWith(c.sep) && (r += c.basename(t)) : r = c.basename(t), a.cp(t, this.getPath(r), i);
56
+ }
57
+ mkdir(t) {
58
+ return a.mkdir(this.getPath(t), { recursive: !0 });
59
+ }
60
+ writeFile(t, r) {
61
+ return a.writeFile(this.getPath(t), r);
62
+ }
63
+ writeJson(t, r) {
64
+ return this.writeFile(t, JSON.stringify(r, null, 2));
65
+ }
66
+ readFile(t, r) {
67
+ return a.readFile(this.getPath(t), r);
68
+ }
69
+ async [Symbol.asyncDispose]() {
70
+ await this.rm();
71
+ }
72
+ };
73
+ const v = b.realpathSync(F.tmpdir()), D = `fs-fixture-${Date.now()}-${process.pid}`;
74
+ let m = 0;
75
+ const j = n(() => (m += 1, m), "getId");
76
+ var u = class {
77
+ static {
78
+ n(this, "Path");
79
+ }
80
+ path;
81
+ constructor(t) {
82
+ this.path = t;
83
+ }
84
+ };
85
+ var f = class extends u {
86
+ static {
87
+ n(this, "Directory");
88
+ }
89
+ };
90
+ var y = class extends u {
91
+ static {
92
+ n(this, "File");
93
+ }
94
+ content;
95
+ constructor(t, r) {
96
+ super(t), this.content = r;
97
+ }
98
+ };
99
+ var l = class {
100
+ static {
101
+ n(this, "Symlink");
102
+ }
103
+ target;
104
+ type;
105
+ path;
106
+ constructor(t, r) {
107
+ this.target = t, this.type = r;
108
+ }
109
+ };
110
+ const w = n((s, t, r) => {
111
+ const i = [];
112
+ for (const p in s) {
113
+ if (!Object.hasOwn(s, p)) continue;
114
+ const e = c.join(t, p);
115
+ let o = s[p];
116
+ if (typeof o == "function") {
117
+ const g = Object.assign(Object.create(r), { filePath: e }), h = o(g);
118
+ if (h instanceof l) {
119
+ h.path = e, i.push(h);
120
+ continue;
121
+ } else o = h;
122
+ }
123
+ typeof o == "string" ? i.push(new y(e, o)) : i.push(new f(e), ...w(o, e, r));
124
+ }
125
+ return i;
126
+ }, "flattenFileTree");
127
+ n(async (s, t) => {
128
+ const r = t?.tempDir ? c.resolve(t.tempDir) : v, i = c.join(r, `${D}-${j()}/`);
129
+ if (await a.mkdir(i, { recursive: !0 }), s) {
130
+ if (typeof s == "string") await a.cp(s, i, {
131
+ recursive: !0,
132
+ filter: t?.templateFilter
133
+ });
134
+ else if (typeof s == "object") {
135
+ const p = {
136
+ fixturePath: i,
137
+ getPath: n((...e) => c.join(i, ...e), "getPath"),
138
+ symlink: n((e, o) => new l(e, o), "symlink")
139
+ };
140
+ await Promise.all(w(s, i, p).map(async (e) => {
141
+ e instanceof f ? await a.mkdir(e.path, { recursive: !0 }) : e instanceof l ? (await a.mkdir(c.dirname(e.path), { recursive: !0 }), await a.symlink(e.target, e.path, e.type)) : e instanceof y && (await a.mkdir(c.dirname(e.path), { recursive: !0 }), await a.writeFile(e.path, e.content));
142
+ }));
143
+ }
144
+ }
145
+ return new P(i);
146
+ }, "createFixture");
147
+ const nodeRequire = createRequire(import.meta.url);
148
+ function resolveBinaryPath(packageName, binName) {
149
+ let packageJsonPath;
150
+ try {
151
+ packageJsonPath = nodeRequire.resolve(`${packageName}/package.json`);
152
+ } catch (error) {
153
+ throw new Error(`Unable to resolve ${packageName}. Install the package alongside @ccusage/mcp to enable ${packageName} tools.`, { cause: error });
154
+ }
155
+ const packageJson = nodeRequire(packageJsonPath);
156
+ const binField = packageJson.bin ?? packageJson.publishConfig?.bin;
157
+ let binRelative;
158
+ if (typeof binField === "string") binRelative = binField;
159
+ else if (binField != null && typeof binField === "object") binRelative = binName != null && binName !== "" ? binField[binName] : Object.values(binField)[0];
160
+ if (binRelative == null) throw new Error(`Unable to locate ${binName ?? packageName} binary entry in ${packageName}/package.json`);
161
+ const packageDir = c.dirname(packageJsonPath);
162
+ return c.resolve(packageDir, binRelative);
163
+ }
164
+ function createCliInvocation(entryPath) {
165
+ if (entryPath.endsWith(".ts")) return {
166
+ executable: "bun",
167
+ prefixArgs: [entryPath]
168
+ };
169
+ return {
170
+ executable: process$1.execPath,
171
+ prefixArgs: [entryPath]
172
+ };
173
+ }
174
+ async function executeCliCommand(executable, args, env) {
175
+ try {
176
+ const result = await spawn(executable, args, { env: {
177
+ ...process$1.env,
178
+ FORCE_COLOR: "0",
179
+ ...env
180
+ } });
181
+ const output = (result.stdout ?? result.output ?? "").trim();
182
+ if (output === "") throw new Error("CLI command returned empty output");
183
+ return output;
184
+ } catch (error) {
185
+ if (error instanceof SubprocessError) {
186
+ const message = (error.stderr ?? error.stdout ?? error.output ?? error.message).trim();
187
+ throw new Error(message);
188
+ }
189
+ throw error;
190
+ }
191
+ }
192
+ const filterDateSchema = z.string().regex(/^\d{8}$/, "Date must be in YYYYMMDD format");
193
+ const ccusageParametersShape = {
194
+ since: filterDateSchema.optional(),
195
+ until: filterDateSchema.optional(),
196
+ mode: z.enum([
197
+ "auto",
198
+ "calculate",
199
+ "display"
200
+ ]).default("auto").optional(),
201
+ timezone: z.string().optional(),
202
+ locale: z.string().optional()
203
+ };
204
+ const ccusageParametersSchema = z.object(ccusageParametersShape);
205
+ let cachedCcusageInvocation = null;
206
+ function getCcusageInvocation() {
207
+ if (cachedCcusageInvocation != null) return cachedCcusageInvocation;
208
+ const entryPath = resolveBinaryPath("ccusage", "ccusage");
209
+ cachedCcusageInvocation = createCliInvocation(entryPath);
210
+ return cachedCcusageInvocation;
211
+ }
212
+ async function runCcusageCliJson(command, parameters, claudePath) {
213
+ const { executable, prefixArgs } = getCcusageInvocation();
214
+ const cliArgs = [
215
+ ...prefixArgs,
216
+ command,
217
+ "--json"
218
+ ];
219
+ const since = parameters.since;
220
+ if (since != null && since !== "") cliArgs.push("--since", since);
221
+ const until = parameters.until;
222
+ if (until != null && until !== "") cliArgs.push("--until", until);
223
+ const mode = parameters.mode;
224
+ if (mode != null && mode !== "auto") cliArgs.push("--mode", mode);
225
+ const timezone = parameters.timezone;
226
+ if (timezone != null && timezone !== "") cliArgs.push("--timezone", timezone);
227
+ const locale = parameters.locale;
228
+ if (locale != null && locale !== "") cliArgs.push("--locale", locale);
229
+ return executeCliCommand(executable, cliArgs, { CLAUDE_CONFIG_DIR: claudePath });
230
+ }
231
+ async function getCcusageDaily(parameters, claudePath) {
232
+ try {
233
+ const raw = await runCcusageCliJson("daily", parameters, claudePath);
234
+ const parsed = JSON.parse(raw);
235
+ if (Array.isArray(parsed) && parsed.length === 0) return {
236
+ daily: [],
237
+ totals: {}
238
+ };
239
+ return parsed;
240
+ } catch {
241
+ return {
242
+ daily: [],
243
+ totals: {}
244
+ };
245
+ }
246
+ }
247
+ async function getCcusageMonthly(parameters, claudePath) {
248
+ try {
249
+ const raw = await runCcusageCliJson("monthly", parameters, claudePath);
250
+ const parsed = JSON.parse(raw);
251
+ if (Array.isArray(parsed) && parsed.length === 0) return {
252
+ monthly: [],
253
+ totals: {}
254
+ };
255
+ return parsed;
256
+ } catch {
257
+ return {
258
+ monthly: [],
259
+ totals: {}
260
+ };
261
+ }
262
+ }
263
+ async function getCcusageSession(parameters, claudePath) {
264
+ try {
265
+ const raw = await runCcusageCliJson("session", parameters, claudePath);
266
+ const parsed = JSON.parse(raw);
267
+ if (Array.isArray(parsed) && parsed.length === 0) return {
268
+ sessions: [],
269
+ totals: {}
270
+ };
271
+ return parsed;
272
+ } catch {
273
+ return {
274
+ sessions: [],
275
+ totals: {}
276
+ };
277
+ }
278
+ }
279
+ async function getCcusageBlocks(parameters, claudePath) {
280
+ try {
281
+ const raw = await runCcusageCliJson("blocks", parameters, claudePath);
282
+ const parsed = JSON.parse(raw);
283
+ if (Array.isArray(parsed) && parsed.length === 0) return { blocks: [] };
284
+ return parsed;
285
+ } catch {
286
+ return { blocks: [] };
287
+ }
288
+ }
289
+ const codexModelUsageSchema = z.object({
290
+ inputTokens: z.number(),
291
+ cachedInputTokens: z.number(),
292
+ outputTokens: z.number(),
293
+ reasoningOutputTokens: z.number(),
294
+ totalTokens: z.number(),
295
+ isFallback: z.boolean().optional()
296
+ });
297
+ const codexTotalsSchema = z.object({
298
+ inputTokens: z.number(),
299
+ cachedInputTokens: z.number(),
300
+ outputTokens: z.number(),
301
+ reasoningOutputTokens: z.number(),
302
+ totalTokens: z.number(),
303
+ costUSD: z.number()
304
+ });
305
+ const codexDailyRowSchema = z.object({
306
+ date: z.string(),
307
+ inputTokens: z.number(),
308
+ cachedInputTokens: z.number(),
309
+ outputTokens: z.number(),
310
+ reasoningOutputTokens: z.number(),
311
+ totalTokens: z.number(),
312
+ costUSD: z.number(),
313
+ models: z.record(codexModelUsageSchema)
314
+ });
315
+ const codexMonthlyRowSchema = z.object({
316
+ month: z.string(),
317
+ inputTokens: z.number(),
318
+ cachedInputTokens: z.number(),
319
+ outputTokens: z.number(),
320
+ reasoningOutputTokens: z.number(),
321
+ totalTokens: z.number(),
322
+ costUSD: z.number(),
323
+ models: z.record(codexModelUsageSchema)
324
+ });
325
+ const codexDailyResponseSchema = z.object({
326
+ daily: z.array(codexDailyRowSchema),
327
+ totals: codexTotalsSchema.nullable()
328
+ });
329
+ const codexMonthlyResponseSchema = z.object({
330
+ monthly: z.array(codexMonthlyRowSchema),
331
+ totals: codexTotalsSchema.nullable()
332
+ });
333
+ const codexParametersShape = {
334
+ since: z.string().optional(),
335
+ until: z.string().optional(),
336
+ timezone: z.string().optional(),
337
+ locale: z.string().optional(),
338
+ offline: z.boolean().optional()
339
+ };
340
+ const codexParametersSchema = z.object(codexParametersShape);
341
+ let cachedCodexInvocation = null;
342
+ function getCodexInvocation() {
343
+ if (cachedCodexInvocation != null) return cachedCodexInvocation;
344
+ const entryPath = resolveBinaryPath("@ccusage/codex", "ccusage-codex");
345
+ cachedCodexInvocation = createCliInvocation(entryPath);
346
+ return cachedCodexInvocation;
347
+ }
348
+ async function runCodexCliJson(command, parameters) {
349
+ const { executable, prefixArgs } = getCodexInvocation();
350
+ const cliArgs = [
351
+ ...prefixArgs,
352
+ command,
353
+ "--json"
354
+ ];
355
+ const since = parameters.since;
356
+ if (since != null && since !== "") cliArgs.push("--since", since);
357
+ const until = parameters.until;
358
+ if (until != null && until !== "") cliArgs.push("--until", until);
359
+ const timezone = parameters.timezone;
360
+ if (timezone != null && timezone !== "") cliArgs.push("--timezone", timezone);
361
+ const locale = parameters.locale;
362
+ if (locale != null && locale !== "") cliArgs.push("--locale", locale);
363
+ if (parameters.offline === true) cliArgs.push("--offline");
364
+ else if (parameters.offline === false) cliArgs.push("--no-offline");
365
+ return executeCliCommand(executable, cliArgs, {});
366
+ }
367
+ async function getCodexDaily(parameters) {
368
+ const raw = await runCodexCliJson("daily", parameters);
369
+ return codexDailyResponseSchema.parse(JSON.parse(raw));
370
+ }
371
+ async function getCodexMonthly(parameters) {
372
+ const raw = await runCodexCliJson("monthly", parameters);
373
+ return codexMonthlyResponseSchema.parse(JSON.parse(raw));
374
+ }
375
+ function defaultOptions() {
376
+ const paths = getClaudePaths();
377
+ if (paths.length === 0) throw new Error("No valid Claude path found. Ensure getClaudePaths() returns at least one valid path.");
378
+ return { claudePath: paths[0] };
379
+ }
380
+ function createMcpServer(options) {
381
+ const server = new McpServer({
382
+ name,
383
+ version
384
+ });
385
+ const { claudePath = "" } = options ?? defaultOptions();
386
+ if (claudePath === "") throw new Error("Claude path is required");
387
+ server.registerTool("daily", {
388
+ description: "Show usage report grouped by date",
389
+ inputSchema: ccusageParametersShape
390
+ }, async (args) => {
391
+ const parameters = ccusageParametersSchema.parse(args);
392
+ const jsonOutput = await getCcusageDaily(parameters, claudePath);
393
+ return { content: [{
394
+ type: "text",
395
+ text: JSON.stringify(jsonOutput, null, 2)
396
+ }] };
397
+ });
398
+ server.registerTool("session", {
399
+ description: "Show usage report grouped by conversation session",
400
+ inputSchema: ccusageParametersShape
401
+ }, async (args) => {
402
+ const parameters = ccusageParametersSchema.parse(args);
403
+ const jsonOutput = await getCcusageSession(parameters, claudePath);
404
+ return { content: [{
405
+ type: "text",
406
+ text: JSON.stringify(jsonOutput, null, 2)
407
+ }] };
408
+ });
409
+ server.registerTool("monthly", {
410
+ description: "Show usage report grouped by month",
411
+ inputSchema: ccusageParametersShape
412
+ }, async (args) => {
413
+ const parameters = ccusageParametersSchema.parse(args);
414
+ const jsonOutput = await getCcusageMonthly(parameters, claudePath);
415
+ return { content: [{
416
+ type: "text",
417
+ text: JSON.stringify(jsonOutput, null, 2)
418
+ }] };
419
+ });
420
+ server.registerTool("blocks", {
421
+ description: "Show usage report grouped by session billing blocks",
422
+ inputSchema: ccusageParametersShape
423
+ }, async (args) => {
424
+ const parameters = ccusageParametersSchema.parse(args);
425
+ const jsonOutput = await getCcusageBlocks(parameters, claudePath);
426
+ return { content: [{
427
+ type: "text",
428
+ text: JSON.stringify(jsonOutput, null, 2)
429
+ }] };
430
+ });
431
+ server.registerTool("codex-daily", {
432
+ description: "Show Codex usage grouped by day",
433
+ inputSchema: codexParametersShape
434
+ }, async (args) => {
435
+ const parameters = codexParametersSchema.parse(args);
436
+ const codexDaily = await getCodexDaily(parameters);
437
+ return { content: [{
438
+ type: "text",
439
+ text: JSON.stringify(codexDaily, null, 2)
440
+ }] };
441
+ });
442
+ server.registerTool("codex-monthly", {
443
+ description: "Show Codex usage grouped by month",
444
+ inputSchema: codexParametersShape
445
+ }, async (args) => {
446
+ const parameters = codexParametersSchema.parse(args);
447
+ const codexMonthly = await getCodexMonthly(parameters);
448
+ return { content: [{
449
+ type: "text",
450
+ text: JSON.stringify(codexMonthly, null, 2)
451
+ }] };
452
+ });
453
+ return server;
454
+ }
455
+ async function startMcpServerStdio(server) {
456
+ const transport = new StdioServerTransport();
457
+ await server.connect(transport);
458
+ }
459
+ function createMcpHttpApp(options) {
460
+ const app = new Hono();
461
+ const mcpServer = createMcpServer(options ?? defaultOptions());
462
+ app.all("/", async (c$1) => {
463
+ const transport = new StreamableHTTPTransport();
464
+ await mcpServer.connect(transport);
465
+ return transport.handleRequest(c$1);
466
+ });
467
+ return app;
468
+ }
469
+ const MCP_DEFAULT_PORT = 8080;
470
+ const mcpCommand = define({
471
+ name: "mcp",
472
+ description: "Start MCP server with usage reporting tools",
473
+ args: {
474
+ mode: {
475
+ type: "enum",
476
+ short: "m",
477
+ description: "Cost calculation mode for usage reports",
478
+ choices: [
479
+ "auto",
480
+ "calculate",
481
+ "display"
482
+ ],
483
+ default: "auto"
484
+ },
485
+ type: {
486
+ type: "enum",
487
+ short: "t",
488
+ description: "Transport type for MCP server",
489
+ choices: ["stdio", "http"],
490
+ default: "stdio"
491
+ },
492
+ port: {
493
+ type: "number",
494
+ short: "p",
495
+ description: `Port for HTTP transport (default: ${MCP_DEFAULT_PORT})`,
496
+ default: MCP_DEFAULT_PORT
497
+ }
498
+ },
499
+ async run(ctx) {
500
+ const { type: mcpType, mode, port } = ctx.values;
501
+ if (mcpType === "stdio") logger.level = 0;
502
+ const paths = getClaudePaths();
503
+ if (paths.length === 0) {
504
+ logger.error("No valid Claude data directory found");
505
+ throw new Error("No valid Claude data directory found");
506
+ }
507
+ const options = {
508
+ claudePath: paths.at(0),
509
+ mode
510
+ };
511
+ switch (mcpType) {
512
+ case "stdio": {
513
+ const server = createMcpServer(options);
514
+ await startMcpServerStdio(server);
515
+ return;
516
+ }
517
+ case "http": {
518
+ const app = createMcpHttpApp(options);
519
+ serve({
520
+ fetch: app.fetch,
521
+ port
522
+ });
523
+ logger.info(`MCP server is running on http://localhost:${port}`);
524
+ return;
525
+ }
526
+ default: throw new Error(`Unsupported MCP type: ${mcpType}`);
527
+ }
528
+ }
529
+ });
530
+ async function runCli(argv = process$1.argv.slice(2)) {
531
+ await cli(argv, mcpCommand, {
532
+ name,
533
+ version,
534
+ description,
535
+ subCommands: /* @__PURE__ */ new Map()
536
+ });
537
+ }
538
+ runCli();
539
+ export {};
package/package.json CHANGED
@@ -1,12 +1,44 @@
1
1
  {
2
2
  "name": "@ccusage/mcp",
3
- "version": "0.0.1",
4
- "module": "index.ts",
3
+ "version": "17.0.0",
4
+ "description": "MCP server implementation for ccusage data",
5
+ "homepage": "https://github.com/ryoppippi/ccusage#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/ryoppippi/ccusage/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/ryoppippi/ccusage.git"
12
+ },
13
+ "funding": "https://github.com/ryoppippi/ccusage?sponsor=1",
14
+ "license": "MIT",
15
+ "author": "ryoppippi",
5
16
  "type": "module",
6
- "devDependencies": {
7
- "@types/bun": "latest"
17
+ "exports": {
18
+ ".": "./dist/index.js",
19
+ "./package.json": "./package.json"
20
+ },
21
+ "main": "./dist/index.js",
22
+ "module": "./dist/index.js",
23
+ "types": "./dist/index.d.ts",
24
+ "bin": {
25
+ "ccusage-mcp": "./dist/index.js"
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "dependencies": {
31
+ "@hono/mcp": "^0.1.4",
32
+ "@hono/node-server": "^1.19.2",
33
+ "@modelcontextprotocol/sdk": "^1.17.3",
34
+ "gunshi": "^0.26.3",
35
+ "hono": "^4.9.2",
36
+ "nano-spawn": "^1.0.2",
37
+ "zod": "^3.25.67",
38
+ "ccusage": "17.0.0",
39
+ "@ccusage/codex": "17.0.0"
8
40
  },
9
- "peerDependencies": {
10
- "typescript": "^5"
41
+ "engines": {
42
+ "node": ">=20.19.4"
11
43
  }
12
- }
44
+ }
package/CLAUDE.md DELETED
@@ -1,107 +0,0 @@
1
- ---
2
-
3
- Default to using Bun instead of Node.js.
4
-
5
- - Use `bun <file>` instead of `node <file>` or `ts-node <file>`
6
- - Use `bun test` instead of `jest` or `vitest`
7
- - Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
8
- - Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
9
- - Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
10
- - Bun automatically loads .env, so don't use dotenv.
11
-
12
- ## APIs
13
-
14
- - `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
15
- - `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
16
- - `Bun.redis` for Redis. Don't use `ioredis`.
17
- - `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
18
- - `WebSocket` is built-in. Don't use `ws`.
19
- - Prefer `Bun.file` over `node:fs`'s readFile/writeFile
20
- - Bun.$`ls` instead of execa.
21
-
22
- ## Testing
23
-
24
- Use `bun test` to run tests.
25
-
26
- ```ts#index.test.ts
27
- import { test, expect } from "bun:test";
28
-
29
- test("hello world", () => {
30
- expect(1).toBe(1);
31
- });
32
- ```
33
-
34
- ## Frontend
35
-
36
- Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
37
-
38
- Server:
39
-
40
- ```ts#index.ts
41
- import index from "./index.html"
42
-
43
- Bun.serve({
44
- routes: {
45
- "/": index,
46
- "/api/users/:id": {
47
- GET: (req) => {
48
- return new Response(JSON.stringify({ id: req.params.id }));
49
- },
50
- },
51
- },
52
- // optional websocket support
53
- websocket: {
54
- open: (ws) => {
55
- ws.send("Hello, world!");
56
- },
57
- message: (ws, message) => {
58
- ws.send(message);
59
- },
60
- close: (ws) => {
61
- // handle close
62
- }
63
- },
64
- development: {
65
- hmr: true,
66
- console: true,
67
- }
68
- })
69
- ```
70
-
71
- HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
72
-
73
- ```html#index.html
74
- <html>
75
- <body>
76
- <h1>Hello, world!</h1>
77
- <script type="module" src="./frontend.tsx"></script>
78
- </body>
79
- </html>
80
- ```
81
-
82
- With the following `frontend.tsx`:
83
-
84
- ```tsx#frontend.tsx
85
- import React from "react";
86
-
87
- // import .css files directly and it works
88
- import './index.css';
89
-
90
- import { createRoot } from "react-dom/client";
91
-
92
- const root = createRoot(document.body);
93
-
94
- export default function Frontend() {
95
- return <h1>Hello, world!</h1>;
96
- }
97
-
98
- root.render(<Frontend />);
99
- ```
100
-
101
- Then, run index.ts
102
-
103
- ```sh
104
- bun --hot ./index.ts
105
- ```
106
-
107
- For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`.
package/bun.lock DELETED
@@ -1,29 +0,0 @@
1
- {
2
- "lockfileVersion": 1,
3
- "workspaces": {
4
- "": {
5
- "name": "mk",
6
- "devDependencies": {
7
- "@types/bun": "latest",
8
- },
9
- "peerDependencies": {
10
- "typescript": "^5",
11
- },
12
- },
13
- },
14
- "packages": {
15
- "@types/bun": ["@types/bun@1.2.20", "", { "dependencies": { "bun-types": "1.2.20" } }, "sha512-dX3RGzQ8+KgmMw7CsW4xT5ITBSCrSbfHc36SNT31EOUg/LA9JWq0VDdEXDRSe1InVWpd2yLUM1FUF/kEOyTzYA=="],
16
-
17
- "@types/node": ["@types/node@24.3.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow=="],
18
-
19
- "@types/react": ["@types/react@19.1.10", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg=="],
20
-
21
- "bun-types": ["bun-types@1.2.20", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-pxTnQYOrKvdOwyiyd/7sMt9yFOenN004Y6O4lCcCUoKVej48FS5cvTw9geRaEcB9TsDZaJKAxPTVvi8tFsVuXA=="],
22
-
23
- "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
24
-
25
- "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
26
-
27
- "undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
28
- }
29
- }
package/index.ts DELETED
@@ -1 +0,0 @@
1
- console.log("hello ccusage");
package/tsconfig.json DELETED
@@ -1,29 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- // Environment setup & latest features
4
- "lib": ["ESNext"],
5
- "target": "ESNext",
6
- "module": "Preserve",
7
- "moduleDetection": "force",
8
- "jsx": "react-jsx",
9
- "allowJs": true,
10
-
11
- // Bundler mode
12
- "moduleResolution": "bundler",
13
- "allowImportingTsExtensions": true,
14
- "verbatimModuleSyntax": true,
15
- "noEmit": true,
16
-
17
- // Best practices
18
- "strict": true,
19
- "skipLibCheck": true,
20
- "noFallthroughCasesInSwitch": true,
21
- "noUncheckedIndexedAccess": true,
22
- "noImplicitOverride": true,
23
-
24
- // Some stricter flags (disabled by default)
25
- "noUnusedLocals": false,
26
- "noUnusedParameters": false,
27
- "noPropertyAccessFromIndexSignature": false
28
- }
29
- }