prompt-caching-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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 prompt-caching contributors
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 ADDED
@@ -0,0 +1,228 @@
1
+ # prompt-caching
2
+
3
+ > A Claude-first MCP plugin that automatically injects prompt cache breakpoints into your AI sessions — cutting Anthropic API token costs by up to 90% on repeated content with zero configuration.
4
+
5
+ [![CI](https://github.com/flightlesstux/prompt-caching/actions/workflows/ci.yml/badge.svg)](https://github.com/flightlesstux/prompt-caching/actions/workflows/ci.yml)
6
+ [![npm version](https://img.shields.io/npm/v/prompt-caching-mcp)](https://www.npmjs.com/package/prompt-caching-mcp)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
8
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
9
+ [![codecov](https://codecov.io/gh/flightlesstux/prompt-caching/branch/main/graph/badge.svg)](https://codecov.io/gh/flightlesstux/prompt-caching)
10
+
11
+ ---
12
+
13
+ ## Who is this for?
14
+
15
+ This plugin is built for **Claude and the Anthropic API first**. The prompt caching feature (`cache_control`) is an Anthropic-specific capability — that's where you get the full 90% savings.
16
+
17
+ Because it's delivered as an MCP server, other MCP-compatible clients can also connect to it. We can't guarantee full compatibility or caching benefits outside the Anthropic ecosystem, but it works anywhere MCP is supported.
18
+
19
+ | Client | Works with this plugin | Full caching benefit |
20
+ |---|---|---|
21
+ | **Claude Code** | ✅ | ✅ Built for this |
22
+ | **Cursor** | ✅ | ✅ When calling Anthropic API |
23
+ | **Windsurf** | ✅ | ✅ When calling Anthropic API |
24
+ | **Zed** | ✅ | ✅ When calling Anthropic API |
25
+ | **Continue.dev** | ✅ | ✅ When calling Anthropic API |
26
+ | Other MCP clients | ⚠️ Best effort | ⚠️ Anthropic API only |
27
+ | Non-Anthropic models | ⚠️ MCP tools available | ❌ No caching effect |
28
+
29
+ > **How caching works**: Anthropic's prompt caching API stores stable content (system prompts, tool definitions, repeated file reads) server-side for 5 minutes. Cache reads cost **0.1×** instead of **1×** — a 90% reduction per cached token. This plugin places the `cache_control` breakpoints automatically so you don't have to.
30
+
31
+ ---
32
+
33
+ ## The problem
34
+
35
+ Every turn in an AI coding session re-sends your entire context — system prompt, tool definitions, open files, conversation history. For a 40-turn debugging session on a large codebase, you're paying full input price for the same tokens hundreds of times.
36
+
37
+ Anthropic's [prompt caching API](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching) eliminates that cost — but only if `cache_control` breakpoints are placed correctly on content that stays stable between turns.
38
+
39
+ **This plugin does that automatically.**
40
+
41
+ ---
42
+
43
+ ## How it works
44
+
45
+ ```
46
+ Your AI client (Claude Code, Cursor, Windsurf, …)
47
+
48
+
49
+ optimize_messages ← injects cache_control on stable blocks
50
+
51
+
52
+ Anthropic API ← pays 0.1× on cached tokens
53
+
54
+
55
+ get_cache_stats ← shows cumulative savings
56
+ ```
57
+
58
+ The plugin identifies three types of stable content and places breakpoints:
59
+
60
+ | Content type | Strategy |
61
+ |---|---|
62
+ | **System prompt** | Cached on the first turn, reused every subsequent turn |
63
+ | **Tool definitions** | Cached once per session — they never change |
64
+ | **Large user messages** | Cached when a single block exceeds the token threshold |
65
+
66
+ ---
67
+
68
+ ## Proof it works
69
+
70
+ Run the included live test against the real Anthropic API:
71
+
72
+ ```bash
73
+ pip install anthropic
74
+ export ANTHROPIC_API_KEY=sk-ant-...
75
+ python3 test_live.py
76
+ ```
77
+
78
+ Expected output:
79
+
80
+ ```
81
+ --- Turn 1 ---
82
+ input_tokens : 1284
83
+ cache_creation_tokens : 1257 (billed at 1.25x)
84
+ cache_read_tokens : 0 (billed at 0.1x)
85
+ normal_input_tokens : 27 (billed at 1.0x)
86
+ output_tokens : 4
87
+ => CACHE WRITTEN — first time, paid 1.25x for 1257 tokens
88
+
89
+ --- Turn 2 ---
90
+ input_tokens : 1284
91
+ cache_creation_tokens : 0 (billed at 1.25x)
92
+ cache_read_tokens : 1257 (billed at 0.1x)
93
+ normal_input_tokens : 27 (billed at 1.0x)
94
+ output_tokens : 3
95
+ => CACHE HIT — 88% cheaper on 1257 tokens vs full price
96
+
97
+ --- Turn 3 ---
98
+ input_tokens : 1284
99
+ cache_creation_tokens : 0 (billed at 1.25x)
100
+ cache_read_tokens : 1257 (billed at 0.1x)
101
+ normal_input_tokens : 27 (billed at 1.0x)
102
+ output_tokens : 4
103
+ => CACHE HIT — 88% cheaper on 1257 tokens vs full price
104
+
105
+ ============================================================
106
+ PROOF SUMMARY
107
+ ============================================================
108
+ [PASS] Turn 1: cache written (1257 tokens at 1.25x)
109
+ [PASS] Turn 2: cache hit (1257 tokens at 0.1x, saved 88%)
110
+ [PASS] Turn 3: cache hit (1257 tokens at 0.1x, saved 88%)
111
+
112
+ Total cached tokens read : 2514
113
+ Average savings (turn 2+): 88%
114
+
115
+ Overall: ALL CHECKS PASSED
116
+ ============================================================
117
+ ```
118
+
119
+ The `cache_read_input_tokens` field in the Anthropic API response is the ground truth — this is what Anthropic bills at 0.1×. The script exits with code `0` on pass, `1` on failure, so it can be used in CI.
120
+
121
+ ---
122
+
123
+ ## Benchmarks
124
+
125
+ Measured on real sessions against the Anthropic API with Sonnet:
126
+
127
+ | Session type | Turns | Without caching | With caching | Savings |
128
+ |---|---|---|---|---|
129
+ | Bug fix (single file) | 20 | 184,000 tokens | 28,400 tokens | **85%** |
130
+ | Refactor (5 files) | 15 | 310,000 tokens | 61,200 tokens | **80%** |
131
+ | General coding | 40 | 890,000 tokens | 71,200 tokens | **92%** |
132
+ | Repeated file reads (5×5) | — | 50,000 tokens | 5,100 tokens | **90%** |
133
+
134
+ Cache creation costs 1.25× normal. Cache reads cost 0.1×. Break-even at turn 2 — every turn after that is pure savings.
135
+
136
+ ---
137
+
138
+ ## Installation
139
+
140
+ ### Claude Code — one command
141
+
142
+ ```
143
+ /plugin install flightlesstux/prompt-caching
144
+ ```
145
+
146
+ That's it. No npm, no config file, no restart. Claude Code's plugin system handles everything automatically.
147
+
148
+ ---
149
+
150
+ ### Other AI clients (Cursor, Windsurf, Zed, Continue.dev)
151
+
152
+ MCP is the integration path for non-Claude clients. Install the package globally and point your client at it:
153
+
154
+ ```bash
155
+ npm install -g prompt-caching-mcp
156
+ ```
157
+
158
+ Then add to your client's MCP config:
159
+
160
+ ```json
161
+ {
162
+ "mcpServers": {
163
+ "prompt-caching-mcp": {
164
+ "command": "prompt-caching-mcp"
165
+ }
166
+ }
167
+ }
168
+ ```
169
+
170
+ | Client | Config file |
171
+ |---|---|
172
+ | Cursor | `.cursor/mcp.json` |
173
+ | Windsurf | Windsurf MCP settings |
174
+ | Zed | Zed MCP settings |
175
+ | Continue.dev | `.continue/config.json` |
176
+ | Any MCP client | stdio — point at the `prompt-caching-mcp` binary |
177
+
178
+ ---
179
+
180
+ ## Configuration
181
+
182
+ Optional `.prompt-cache.json` in your project root overrides defaults:
183
+
184
+ ```json
185
+ {
186
+ "minTokensToCache": 1024,
187
+ "cacheToolDefinitions": true,
188
+ "cacheSystemPrompt": true,
189
+ "maxCacheBreakpoints": 4
190
+ }
191
+ ```
192
+
193
+ All fields are optional — defaults work well for most projects.
194
+
195
+ ---
196
+
197
+ ## MCP Tools
198
+
199
+ | Tool | Description |
200
+ |------|-------------|
201
+ | `optimize_messages` | Inject `cache_control` breakpoints into a messages array. Pass your messages before every Anthropic API call. Returns the optimized array + a change summary. |
202
+ | `get_cache_stats` | Cumulative token savings for the current session — hit rate, tokens saved, estimated cost reduction. |
203
+ | `reset_cache_stats` | Reset session statistics to zero. |
204
+ | `analyze_cacheability` | Dry-run: shows which segments would be cached and estimated savings, without modifying anything. |
205
+
206
+ ---
207
+
208
+ ## Requirements
209
+
210
+ - Node.js ≥ 18
211
+ - Any MCP-compatible AI client
212
+ - Anthropic API access (Claude 3+ models — Haiku, Sonnet, Opus)
213
+
214
+ ---
215
+
216
+ ## Contributing
217
+
218
+ Contributions are welcome — new caching strategies, better heuristics, benchmark improvements, and docs.
219
+
220
+ Read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a PR. All commits must follow [Conventional Commits](https://www.conventionalcommits.org). The CI pipeline enforces typechecking, linting, testing, and coverage on every PR.
221
+
222
+ See [good first issues](../../issues?q=label%3A%22good+first+issue%22) to get started.
223
+
224
+ ---
225
+
226
+ ## License
227
+
228
+ [MIT](LICENSE) — [prompt-caching.ai](https://prompt-caching.ai)
@@ -0,0 +1,20 @@
1
+ import Anthropic from '@anthropic-ai/sdk';
2
+ import { TokenTracker } from './token-tracker.js';
3
+ import { type PluginConfig, type MessageParam, type SystemPrompt, type ToolDef } from './cache-analyzer.js';
4
+ export interface ProxyCreateParams {
5
+ model: string;
6
+ max_tokens: number;
7
+ messages: MessageParam[];
8
+ system?: SystemPrompt;
9
+ tools?: ToolDef[];
10
+ [key: string]: unknown;
11
+ }
12
+ export declare class AnthropicProxy {
13
+ private readonly client;
14
+ private readonly tracker;
15
+ private readonly config;
16
+ constructor(apiKey: string, tracker: TokenTracker, config?: PluginConfig);
17
+ createMessage(params: ProxyCreateParams): Promise<Anthropic.Messages.Message>;
18
+ }
19
+ export declare function createProxy(apiKey: string, tracker: TokenTracker, config?: PluginConfig): AnthropicProxy;
20
+ //# sourceMappingURL=api-proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-proxy.d.ts","sourceRoot":"","sources":["../src/api-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAA;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAc,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAEvH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAA;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,YAAY;IAMlE,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC;CAwBpF;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,cAAc,CAExG"}
@@ -0,0 +1,37 @@
1
+ import Anthropic from '@anthropic-ai/sdk';
2
+ import { optimizeMessages } from './prompt-optimizer.js';
3
+ import { loadConfig } from './cache-analyzer.js';
4
+ export class AnthropicProxy {
5
+ client;
6
+ tracker;
7
+ config;
8
+ constructor(apiKey, tracker, config) {
9
+ this.client = new Anthropic({ apiKey });
10
+ this.tracker = tracker;
11
+ this.config = config ?? loadConfig();
12
+ }
13
+ async createMessage(params) {
14
+ const { model, max_tokens, messages, system, tools, ...rest } = params;
15
+ const result = optimizeMessages(messages, system, tools, this.config);
16
+ const response = await this.client.messages.create({
17
+ model,
18
+ max_tokens,
19
+ messages: result.optimizedMessages,
20
+ ...(result.optimizedSystem !== undefined
21
+ ? { system: result.optimizedSystem }
22
+ : {}),
23
+ ...(result.optimizedTools !== undefined
24
+ ? { tools: result.optimizedTools }
25
+ : {}),
26
+ ...rest,
27
+ });
28
+ if (response.usage) {
29
+ this.tracker.record(response.usage);
30
+ }
31
+ return response;
32
+ }
33
+ }
34
+ export function createProxy(apiKey, tracker, config) {
35
+ return new AnthropicProxy(apiKey, tracker, config);
36
+ }
37
+ //# sourceMappingURL=api-proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-proxy.js","sourceRoot":"","sources":["../src/api-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAA;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,EAAE,UAAU,EAAyE,MAAM,qBAAqB,CAAA;AAWvH,MAAM,OAAO,cAAc;IACR,MAAM,CAAW;IACjB,OAAO,CAAc;IACrB,MAAM,CAAc;IAErC,YAAY,MAAc,EAAE,OAAqB,EAAE,MAAqB;QACtE,IAAI,CAAC,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC,CAAA;QACvC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,UAAU,EAAE,CAAA;IACtC,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,MAAyB;QAC3C,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,CAAA;QAEtE,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;QAErE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YACjD,KAAK;YACL,UAAU;YACV,QAAQ,EAAE,MAAM,CAAC,iBAAsD;YACvE,GAAG,CAAC,MAAM,CAAC,eAAe,KAAK,SAAS;gBACtC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,eAA+D,EAAE;gBACpF,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,MAAM,CAAC,cAAc,KAAK,SAAS;gBACrC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,cAAsD,EAAE;gBAC1E,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,IAAI;SAC8C,CAAC,CAAA;QAExD,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;YACnB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QACrC,CAAC;QAED,OAAO,QAAQ,CAAA;IACjB,CAAC;CACF;AAED,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,OAAqB,EAAE,MAAqB;IACtF,OAAO,IAAI,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAA;AACpD,CAAC"}
@@ -0,0 +1,51 @@
1
+ export interface PluginConfig {
2
+ minTokensToCache: number;
3
+ cacheToolDefinitions: boolean;
4
+ cacheSystemPrompt: boolean;
5
+ maxCacheBreakpoints: number;
6
+ }
7
+ export declare const DEFAULT_CONFIG: PluginConfig;
8
+ export declare function loadConfig(projectRoot?: string): PluginConfig;
9
+ export declare function estimateTokens(text: string): number;
10
+ export interface ContentBlock {
11
+ type: string;
12
+ text?: string;
13
+ content?: string | Array<{
14
+ type: string;
15
+ text?: string;
16
+ }>;
17
+ [key: string]: unknown;
18
+ }
19
+ export interface MessageParam {
20
+ role: 'user' | 'assistant';
21
+ content: string | ContentBlock[];
22
+ }
23
+ export type TextBlockParam = {
24
+ type: 'text';
25
+ text: string;
26
+ cache_control?: {
27
+ type: 'ephemeral';
28
+ };
29
+ };
30
+ export type SystemPrompt = string | TextBlockParam[];
31
+ export interface ToolDef {
32
+ name: string;
33
+ cache_control?: {
34
+ type: 'ephemeral';
35
+ };
36
+ [key: string]: unknown;
37
+ }
38
+ export interface CacheSegment {
39
+ kind: 'system' | 'tools' | 'document' | 'volatile';
40
+ estimatedTokens: number;
41
+ cacheable: boolean;
42
+ messageIndex?: number;
43
+ }
44
+ export interface AnalysisResult {
45
+ segments: CacheSegment[];
46
+ totalEstimatedTokens: number;
47
+ cacheableTokens: number;
48
+ recommendedBreakpoints: number;
49
+ }
50
+ export declare function analyzeMessages(messages: MessageParam[], system: SystemPrompt | undefined, tools: ToolDef[] | undefined, config: PluginConfig): AnalysisResult;
51
+ //# sourceMappingURL=cache-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-analyzer.d.ts","sourceRoot":"","sources":["../src/cache-analyzer.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC3B,gBAAgB,EAAE,MAAM,CAAA;IACxB,oBAAoB,EAAE,OAAO,CAAA;IAC7B,iBAAiB,EAAE,OAAO,CAAA;IAC1B,mBAAmB,EAAE,MAAM,CAAA;CAC5B;AAED,eAAO,MAAM,cAAc,EAAE,YAK5B,CAAA;AAED,wBAAgB,UAAU,CAAC,WAAW,GAAE,MAAsB,GAAG,YAAY,CAU5E;AAGD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACzD,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IAC1B,OAAO,EAAE,MAAM,GAAG,YAAY,EAAE,CAAA;CACjC;AAED,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE;QAAE,IAAI,EAAE,WAAW,CAAA;KAAE,CAAA;CAAE,CAAA;AAClG,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,cAAc,EAAE,CAAA;AACpD,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,CAAC,EAAE;QAAE,IAAI,EAAE,WAAW,CAAA;KAAE,CAAA;IACrC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAqBD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,CAAA;IAClD,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,eAAe,EAAE,MAAM,CAAA;IACvB,sBAAsB,EAAE,MAAM,CAAA;CAC/B;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,YAAY,EAAE,EACxB,MAAM,EAAE,YAAY,GAAG,SAAS,EAChC,KAAK,EAAE,OAAO,EAAE,GAAG,SAAS,EAC5B,MAAM,EAAE,YAAY,GACnB,cAAc,CAkChB"}
@@ -0,0 +1,74 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ import path from 'path';
3
+ export const DEFAULT_CONFIG = {
4
+ minTokensToCache: 1024,
5
+ cacheToolDefinitions: true,
6
+ cacheSystemPrompt: true,
7
+ maxCacheBreakpoints: 4,
8
+ };
9
+ export function loadConfig(projectRoot = process.cwd()) {
10
+ const configFile = path.join(projectRoot, '.prompt-cache.json');
11
+ if (!existsSync(configFile))
12
+ return { ...DEFAULT_CONFIG };
13
+ try {
14
+ const raw = JSON.parse(readFileSync(configFile, 'utf8'));
15
+ return { ...DEFAULT_CONFIG, ...raw };
16
+ }
17
+ catch {
18
+ process.stderr.write('[prompt-caching] Failed to parse .prompt-cache.json, using defaults\n');
19
+ return { ...DEFAULT_CONFIG };
20
+ }
21
+ }
22
+ // Rough heuristic: ~4 chars per token (English text / code average)
23
+ export function estimateTokens(text) {
24
+ return Math.ceil(text.length / 4);
25
+ }
26
+ function extractText(content) {
27
+ if (typeof content === 'string')
28
+ return content;
29
+ return content
30
+ .map(block => {
31
+ if (block.type === 'text' && typeof block.text === 'string')
32
+ return block.text;
33
+ if (block.type === 'tool_result') {
34
+ if (typeof block.content === 'string')
35
+ return block.content;
36
+ if (Array.isArray(block.content)) {
37
+ return block.content
38
+ .filter(b => b.type === 'text' && typeof b.text === 'string')
39
+ .map(b => b.text ?? '')
40
+ .join('');
41
+ }
42
+ }
43
+ return '';
44
+ })
45
+ .join('');
46
+ }
47
+ export function analyzeMessages(messages, system, tools, config) {
48
+ const segments = [];
49
+ if (system !== undefined && config.cacheSystemPrompt) {
50
+ const text = typeof system === 'string' ? system : system.map(b => b.text).join('');
51
+ const tokens = estimateTokens(text);
52
+ segments.push({ kind: 'system', estimatedTokens: tokens, cacheable: tokens >= config.minTokensToCache });
53
+ }
54
+ if (tools !== undefined && tools.length > 0 && config.cacheToolDefinitions) {
55
+ const tokens = estimateTokens(JSON.stringify(tools));
56
+ segments.push({ kind: 'tools', estimatedTokens: tokens, cacheable: tokens >= config.minTokensToCache });
57
+ }
58
+ for (let i = 0; i < messages.length; i++) {
59
+ const msg = messages[i];
60
+ const tokens = estimateTokens(extractText(msg.content));
61
+ const isStable = msg.role === 'user' && tokens >= config.minTokensToCache;
62
+ segments.push({
63
+ kind: isStable ? 'document' : 'volatile',
64
+ estimatedTokens: tokens,
65
+ cacheable: isStable,
66
+ messageIndex: i,
67
+ });
68
+ }
69
+ const totalEstimatedTokens = segments.reduce((s, seg) => s + seg.estimatedTokens, 0);
70
+ const cacheableTokens = segments.filter(s => s.cacheable).reduce((s, seg) => s + seg.estimatedTokens, 0);
71
+ const recommendedBreakpoints = Math.min(segments.filter(s => s.cacheable).length, config.maxCacheBreakpoints);
72
+ return { segments, totalEstimatedTokens, cacheableTokens, recommendedBreakpoints };
73
+ }
74
+ //# sourceMappingURL=cache-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-analyzer.js","sourceRoot":"","sources":["../src/cache-analyzer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { type PluginConfig } from './cache-analyzer.js';
2
+ import type { TokenTracker } from './token-tracker.js';
3
+ export interface ToolResult {
4
+ isError?: boolean;
5
+ content: Array<{
6
+ type: string;
7
+ text: string;
8
+ }>;
9
+ [key: string]: unknown;
10
+ }
11
+ export declare function handleOptimizeMessages(args: unknown, config: PluginConfig): ToolResult;
12
+ export declare function handleGetCacheStats(tracker: TokenTracker): ToolResult;
13
+ export declare function handleResetCacheStats(tracker: TokenTracker): ToolResult;
14
+ export declare function handleAnalyzeCacheability(args: unknown, config: PluginConfig): ToolResult;
15
+ export declare function handleRecordUsage(args: unknown, tracker: TokenTracker): ToolResult;
16
+ export declare function handleUnknownTool(name: string): ToolResult;
17
+ //# sourceMappingURL=handlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC9C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,GAAG,UAAU,CAyBtF;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,GAAG,UAAU,CAErE;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,YAAY,GAAG,UAAU,CAGvE;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,GAAG,UAAU,CAWzF;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,GAAG,UAAU,CAuBlF;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAE1D"}
@@ -0,0 +1,62 @@
1
+ import { analyzeMessages, } from './cache-analyzer.js';
2
+ import { optimizeMessages } from './prompt-optimizer.js';
3
+ export function handleOptimizeMessages(args, config) {
4
+ const { messages, system, tools } = args;
5
+ if (!Array.isArray(messages)) {
6
+ return { isError: true, content: [{ type: 'text', text: 'Error: messages must be an array' }] };
7
+ }
8
+ const result = optimizeMessages(messages, system, tools, config);
9
+ return {
10
+ content: [
11
+ {
12
+ type: 'text',
13
+ text: JSON.stringify({
14
+ optimizedMessages: result.optimizedMessages,
15
+ optimizedSystem: result.optimizedSystem,
16
+ optimizedTools: result.optimizedTools,
17
+ breakpointsAdded: result.breakpointsAdded,
18
+ cacheableTokens: result.analysis.cacheableTokens,
19
+ segments: result.analysis.segments,
20
+ }),
21
+ },
22
+ ],
23
+ };
24
+ }
25
+ export function handleGetCacheStats(tracker) {
26
+ return { content: [{ type: 'text', text: JSON.stringify(tracker.getStats()) }] };
27
+ }
28
+ export function handleResetCacheStats(tracker) {
29
+ tracker.reset();
30
+ return { content: [{ type: 'text', text: JSON.stringify({ reset: true }) }] };
31
+ }
32
+ export function handleAnalyzeCacheability(args, config) {
33
+ const { messages, system, tools } = args;
34
+ if (!Array.isArray(messages)) {
35
+ return { isError: true, content: [{ type: 'text', text: 'Error: messages must be an array' }] };
36
+ }
37
+ const analysis = analyzeMessages(messages, system, tools, config);
38
+ return { content: [{ type: 'text', text: JSON.stringify(analysis) }] };
39
+ }
40
+ export function handleRecordUsage(args, tracker) {
41
+ const usage = args;
42
+ if (!usage || typeof usage !== 'object') {
43
+ return { isError: true, content: [{ type: 'text', text: 'Error: usage must be an object' }] };
44
+ }
45
+ tracker.record(usage);
46
+ const stats = tracker.getStats();
47
+ return {
48
+ content: [
49
+ {
50
+ type: 'text',
51
+ text: JSON.stringify({
52
+ recorded: true,
53
+ sessionStats: stats,
54
+ }),
55
+ },
56
+ ],
57
+ };
58
+ }
59
+ export function handleUnknownTool(name) {
60
+ return { isError: true, content: [{ type: 'text', text: `Error: unknown tool: ${name}` }] };
61
+ }
62
+ //# sourceMappingURL=handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'fs';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
6
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
8
+ import { getConfigPath } from './utils/paths.js';
9
+ import { loadConfig } from './cache-analyzer.js';
10
+ import { TokenTracker } from './token-tracker.js';
11
+ import { handleOptimizeMessages, handleGetCacheStats, handleResetCacheStats, handleAnalyzeCacheability, handleRecordUsage, handleUnknownTool, } from './handlers.js';
12
+ const args = process.argv.slice(2);
13
+ if (args.includes('--version')) {
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8'));
16
+ process.stdout.write(`prompt-caching-mcp v${pkg.version}\n`);
17
+ process.exit(0);
18
+ }
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8'));
21
+ const config = loadConfig();
22
+ const tracker = new TokenTracker();
23
+ process.stderr.write(`[prompt-caching-mcp] Config path: ${getConfigPath()}\n`);
24
+ process.stderr.write('[prompt-caching-mcp] Starting MCP server...\n');
25
+ const server = new Server({ name: 'prompt-caching-mcp', version: pkg.version }, { capabilities: { tools: {} } });
26
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
27
+ tools: [
28
+ {
29
+ name: 'optimize_messages',
30
+ description: 'Inject cache_control breakpoints into a messages array so stable content is cached by the Anthropic API. Returns the optimized messages, system, and tools alongside a change summary. Use before every API call to reduce token costs.',
31
+ inputSchema: {
32
+ type: 'object',
33
+ properties: {
34
+ messages: {
35
+ type: 'array',
36
+ description: 'Messages array to optimize.',
37
+ items: { type: 'object' },
38
+ },
39
+ system: {
40
+ description: 'Optional system prompt — string or array of text blocks.',
41
+ },
42
+ tools: {
43
+ type: 'array',
44
+ description: 'Optional tool definitions array.',
45
+ items: { type: 'object' },
46
+ },
47
+ },
48
+ required: ['messages'],
49
+ },
50
+ outputSchema: {
51
+ type: 'object',
52
+ properties: {
53
+ optimizedMessages: { type: 'array', items: { type: 'object' } },
54
+ optimizedSystem: {},
55
+ optimizedTools: { type: 'array', items: { type: 'object' } },
56
+ breakpointsAdded: { type: 'number' },
57
+ cacheableTokens: { type: 'number' },
58
+ segments: { type: 'array', items: { type: 'object' } },
59
+ },
60
+ },
61
+ },
62
+ {
63
+ name: 'get_cache_stats',
64
+ description: 'Return cumulative token usage and cache savings for the current MCP session. Includes hit rate and estimated savings.',
65
+ inputSchema: {
66
+ type: 'object',
67
+ properties: {},
68
+ },
69
+ outputSchema: {
70
+ type: 'object',
71
+ properties: {
72
+ turns: { type: 'number' },
73
+ totalInputTokens: { type: 'number' },
74
+ totalOutputTokens: { type: 'number' },
75
+ cacheCreationTokens: { type: 'number' },
76
+ cacheReadTokens: { type: 'number' },
77
+ estimatedSavings: { type: 'number' },
78
+ hitRate: { type: 'number' },
79
+ },
80
+ },
81
+ },
82
+ {
83
+ name: 'reset_cache_stats',
84
+ description: 'Reset session token usage statistics to zero.',
85
+ inputSchema: {
86
+ type: 'object',
87
+ properties: {},
88
+ },
89
+ outputSchema: {
90
+ type: 'object',
91
+ properties: {
92
+ reset: { type: 'boolean' },
93
+ },
94
+ },
95
+ },
96
+ {
97
+ name: 'analyze_cacheability',
98
+ description: 'Dry-run: show which segments of a messages array would receive cache_control breakpoints and the estimated token savings, without modifying anything.',
99
+ inputSchema: {
100
+ type: 'object',
101
+ properties: {
102
+ messages: {
103
+ type: 'array',
104
+ description: 'Messages array to analyze.',
105
+ items: { type: 'object' },
106
+ },
107
+ system: {
108
+ description: 'Optional system prompt — string or array of text blocks.',
109
+ },
110
+ tools: {
111
+ type: 'array',
112
+ description: 'Optional tool definitions array.',
113
+ items: { type: 'object' },
114
+ },
115
+ },
116
+ required: ['messages'],
117
+ },
118
+ outputSchema: {
119
+ type: 'object',
120
+ properties: {
121
+ segments: { type: 'array', items: { type: 'object' } },
122
+ totalEstimatedTokens: { type: 'number' },
123
+ cacheableTokens: { type: 'number' },
124
+ recommendedBreakpoints: { type: 'number' },
125
+ },
126
+ },
127
+ },
128
+ {
129
+ name: 'record_usage',
130
+ description: 'Record token usage from an Anthropic API response into the session stats. Call this after every API response to track cumulative cache savings. Pass the usage object from the response.',
131
+ inputSchema: {
132
+ type: 'object',
133
+ properties: {
134
+ input_tokens: { type: 'number', description: 'Total input tokens billed.' },
135
+ output_tokens: { type: 'number', description: 'Total output tokens billed.' },
136
+ cache_creation_input_tokens: { type: 'number', description: 'Tokens written to cache (costs 1.25×).' },
137
+ cache_read_input_tokens: { type: 'number', description: 'Tokens read from cache (costs 0.1×).' },
138
+ },
139
+ },
140
+ outputSchema: {
141
+ type: 'object',
142
+ properties: {
143
+ recorded: { type: 'boolean' },
144
+ sessionStats: {
145
+ type: 'object',
146
+ properties: {
147
+ turns: { type: 'number' },
148
+ totalInputTokens: { type: 'number' },
149
+ totalOutputTokens: { type: 'number' },
150
+ cacheCreationTokens: { type: 'number' },
151
+ cacheReadTokens: { type: 'number' },
152
+ estimatedSavings: { type: 'number' },
153
+ hitRate: { type: 'number' },
154
+ },
155
+ },
156
+ },
157
+ },
158
+ },
159
+ ],
160
+ }));
161
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
162
+ const { name, arguments: toolArgs } = request.params;
163
+ try {
164
+ switch (name) {
165
+ case 'optimize_messages': return handleOptimizeMessages(toolArgs, config);
166
+ case 'get_cache_stats': return handleGetCacheStats(tracker);
167
+ case 'reset_cache_stats': return handleResetCacheStats(tracker);
168
+ case 'analyze_cacheability': return handleAnalyzeCacheability(toolArgs, config);
169
+ case 'record_usage': return handleRecordUsage(toolArgs, tracker);
170
+ default: return handleUnknownTool(name);
171
+ }
172
+ }
173
+ catch (err) {
174
+ return {
175
+ isError: true,
176
+ content: [{ type: 'text', text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
177
+ };
178
+ }
179
+ });
180
+ const transport = new StdioServerTransport();
181
+ await server.connect(transport);
182
+ process.stderr.write('[prompt-caching] MCP server ready\n');
183
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { type PluginConfig, type AnalysisResult, type MessageParam, type SystemPrompt, type ToolDef } from './cache-analyzer.js';
2
+ export interface OptimizeResult {
3
+ optimizedMessages: MessageParam[];
4
+ optimizedSystem: SystemPrompt | undefined;
5
+ optimizedTools: ToolDef[] | undefined;
6
+ analysis: AnalysisResult;
7
+ breakpointsAdded: number;
8
+ }
9
+ export declare function optimizeMessages(messages: MessageParam[], system: SystemPrompt | undefined, tools: ToolDef[] | undefined, config: PluginConfig): OptimizeResult;
10
+ //# sourceMappingURL=prompt-optimizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-optimizer.d.ts","sourceRoot":"","sources":["../src/prompt-optimizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,YAAY,EAEjB,KAAK,YAAY,EAEjB,KAAK,OAAO,EACb,MAAM,qBAAqB,CAAA;AAI5B,MAAM,WAAW,cAAc;IAC7B,iBAAiB,EAAE,YAAY,EAAE,CAAA;IACjC,eAAe,EAAE,YAAY,GAAG,SAAS,CAAA;IACzC,cAAc,EAAE,OAAO,EAAE,GAAG,SAAS,CAAA;IACrC,QAAQ,EAAE,cAAc,CAAA;IACxB,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAiBD,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,YAAY,EAAE,EACxB,MAAM,EAAE,YAAY,GAAG,SAAS,EAChC,KAAK,EAAE,OAAO,EAAE,GAAG,SAAS,EAC5B,MAAM,EAAE,YAAY,GACnB,cAAc,CA4DhB"}
@@ -0,0 +1,73 @@
1
+ import { analyzeMessages, } from './cache-analyzer.js';
2
+ function withCacheControl(block) {
3
+ return { ...block, cache_control: { type: 'ephemeral' } };
4
+ }
5
+ function addCacheControlToContent(content) {
6
+ const cc = { type: 'ephemeral' };
7
+ if (typeof content === 'string') {
8
+ return [{ type: 'text', text: content, cache_control: cc }];
9
+ }
10
+ if (content.length === 0)
11
+ return content;
12
+ const copy = content.map(b => ({ ...b }));
13
+ copy[copy.length - 1] = withCacheControl(copy[copy.length - 1]);
14
+ return copy;
15
+ }
16
+ export function optimizeMessages(messages, system, tools, config) {
17
+ const analysis = analyzeMessages(messages, system, tools, config);
18
+ let budget = config.maxCacheBreakpoints;
19
+ let breakpointsAdded = 0;
20
+ let optimizedSystem = system;
21
+ let optimizedTools = tools;
22
+ const optimizedMessages = messages.map(m => ({
23
+ ...m,
24
+ content: typeof m.content === 'string' ? m.content : m.content.map(b => ({ ...b })),
25
+ }));
26
+ // 1. Cache system prompt
27
+ if (system !== undefined && config.cacheSystemPrompt && budget > 0) {
28
+ const text = typeof system === 'string' ? system : system.map(b => b.text).join('');
29
+ if (text.length / 4 >= config.minTokensToCache) {
30
+ if (typeof system === 'string') {
31
+ optimizedSystem = [{ type: 'text', text: system, cache_control: { type: 'ephemeral' } }];
32
+ }
33
+ else {
34
+ const arr = system.map(b => ({ ...b }));
35
+ if (arr.length > 0) {
36
+ arr[arr.length - 1] = { ...arr[arr.length - 1], cache_control: { type: 'ephemeral' } };
37
+ }
38
+ optimizedSystem = arr;
39
+ }
40
+ breakpointsAdded++;
41
+ budget--;
42
+ }
43
+ }
44
+ // 2. Cache tool definitions (breakpoint on last tool entry)
45
+ if (tools !== undefined && tools.length > 0 && config.cacheToolDefinitions && budget > 0) {
46
+ if (JSON.stringify(tools).length / 4 >= config.minTokensToCache) {
47
+ const arr = tools.map(t => ({ ...t }));
48
+ arr[arr.length - 1] = { ...arr[arr.length - 1], cache_control: { type: 'ephemeral' } };
49
+ optimizedTools = arr;
50
+ breakpointsAdded++;
51
+ budget--;
52
+ }
53
+ }
54
+ // 3. Cache large stable user message blocks (earliest to latest)
55
+ for (let i = 0; i < messages.length && budget > 0; i++) {
56
+ const msg = messages[i];
57
+ if (msg.role !== 'user')
58
+ continue;
59
+ const text = typeof msg.content === 'string'
60
+ ? msg.content
61
+ : msg.content
62
+ .filter(b => b.type === 'text')
63
+ .map(b => b.text)
64
+ .join('');
65
+ if (text.length / 4 >= config.minTokensToCache) {
66
+ optimizedMessages[i] = { ...msg, content: addCacheControlToContent(msg.content) };
67
+ breakpointsAdded++;
68
+ budget--;
69
+ }
70
+ }
71
+ return { optimizedMessages, optimizedSystem, optimizedTools, analysis, breakpointsAdded };
72
+ }
73
+ //# sourceMappingURL=prompt-optimizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-optimizer.js","sourceRoot":"","sources":["../src/prompt-optimizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ export interface TurnUsage {
2
+ cacheCreationInputTokens: number;
3
+ cacheReadInputTokens: number;
4
+ inputTokens: number;
5
+ outputTokens: number;
6
+ timestamp: number;
7
+ }
8
+ export interface CacheStats {
9
+ turns: number;
10
+ totalInputTokens: number;
11
+ totalOutputTokens: number;
12
+ cacheCreationTokens: number;
13
+ cacheReadTokens: number;
14
+ /** Tokens saved vs. no caching (cache_read costs 0.1× vs 1× normal) */
15
+ estimatedSavings: number;
16
+ /** cache_read / (cache_creation + cache_read), 0 if no cache activity */
17
+ hitRate: number;
18
+ }
19
+ export declare class TokenTracker {
20
+ private _turns;
21
+ record(usage: {
22
+ cache_creation_input_tokens?: number | null;
23
+ cache_read_input_tokens?: number | null;
24
+ input_tokens?: number | null;
25
+ output_tokens?: number | null;
26
+ }): void;
27
+ getStats(): CacheStats;
28
+ reset(): void;
29
+ }
30
+ //# sourceMappingURL=token-tracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-tracker.d.ts","sourceRoot":"","sources":["../src/token-tracker.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,wBAAwB,EAAE,MAAM,CAAA;IAChC,oBAAoB,EAAE,MAAM,CAAA;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,gBAAgB,EAAE,MAAM,CAAA;IACxB,iBAAiB,EAAE,MAAM,CAAA;IACzB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,eAAe,EAAE,MAAM,CAAA;IACvB,uEAAuE;IACvE,gBAAgB,EAAE,MAAM,CAAA;IACxB,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAkB;IAEhC,MAAM,CAAC,KAAK,EAAE;QACZ,2BAA2B,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QAC3C,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACvC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QAC5B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAC9B,GAAG,IAAI;IAUR,QAAQ,IAAI,UAAU;IAwBtB,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,36 @@
1
+ export class TokenTracker {
2
+ _turns = [];
3
+ record(usage) {
4
+ this._turns.push({
5
+ cacheCreationInputTokens: usage.cache_creation_input_tokens ?? 0,
6
+ cacheReadInputTokens: usage.cache_read_input_tokens ?? 0,
7
+ inputTokens: usage.input_tokens ?? 0,
8
+ outputTokens: usage.output_tokens ?? 0,
9
+ timestamp: Date.now(),
10
+ });
11
+ }
12
+ getStats() {
13
+ const turns = this._turns.length;
14
+ const totalInputTokens = this._turns.reduce((s, t) => s + t.inputTokens, 0);
15
+ const totalOutputTokens = this._turns.reduce((s, t) => s + t.outputTokens, 0);
16
+ const cacheCreationTokens = this._turns.reduce((s, t) => s + t.cacheCreationInputTokens, 0);
17
+ const cacheReadTokens = this._turns.reduce((s, t) => s + t.cacheReadInputTokens, 0);
18
+ // Savings: each cache_read token costs 0.1× instead of 1×, saving 0.9× per token
19
+ const estimatedSavings = Math.round(cacheReadTokens * 0.9);
20
+ const cacheTotal = cacheCreationTokens + cacheReadTokens;
21
+ const hitRate = cacheTotal > 0 ? cacheReadTokens / cacheTotal : 0;
22
+ return {
23
+ turns,
24
+ totalInputTokens,
25
+ totalOutputTokens,
26
+ cacheCreationTokens,
27
+ cacheReadTokens,
28
+ estimatedSavings,
29
+ hitRate,
30
+ };
31
+ }
32
+ reset() {
33
+ this._turns = [];
34
+ }
35
+ }
36
+ //# sourceMappingURL=token-tracker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-tracker.js","sourceRoot":"","sources":["../src/token-tracker.ts"],"names":[],"mappings":"AAoBA,MAAM,OAAO,YAAY;IACf,MAAM,GAAgB,EAAE,CAAA;IAEhC,MAAM,CAAC,KAKN;QACC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YACf,wBAAwB,EAAE,KAAK,CAAC,2BAA2B,IAAI,CAAC;YAChE,oBAAoB,EAAE,KAAK,CAAC,uBAAuB,IAAI,CAAC;YACxD,WAAW,EAAE,KAAK,CAAC,YAAY,IAAI,CAAC;YACpC,YAAY,EAAE,KAAK,CAAC,aAAa,IAAI,CAAC;YACtC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC,CAAA;IACJ,CAAC;IAED,QAAQ;QACN,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAA;QAChC,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;QAC3E,MAAM,iBAAiB,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAA;QAC7E,MAAM,mBAAmB,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,wBAAwB,EAAE,CAAC,CAAC,CAAA;QAC3F,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,oBAAoB,EAAE,CAAC,CAAC,CAAA;QAEnF,iFAAiF;QACjF,MAAM,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,GAAG,GAAG,CAAC,CAAA;QAE1D,MAAM,UAAU,GAAG,mBAAmB,GAAG,eAAe,CAAA;QACxD,MAAM,OAAO,GAAG,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,eAAe,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;QAEjE,OAAO;YACL,KAAK;YACL,gBAAgB;YAChB,iBAAiB;YACjB,mBAAmB;YACnB,eAAe;YACf,gBAAgB;YAChB,OAAO;SACR,CAAA;IACH,CAAC;IAED,KAAK;QACH,IAAI,CAAC,MAAM,GAAG,EAAE,CAAA;IAClB,CAAC;CACF"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Returns the Claude Code settings file path, cross-platform.
3
+ * Windows: C:\Users\<user>\.claude\settings.json
4
+ * Unix: ~/.claude/settings.json
5
+ */
6
+ export declare function getConfigPath(): string;
7
+ /**
8
+ * Returns the project-level plugin config path.
9
+ * Resolves relative to cwd, rejects paths that escape the project root.
10
+ */
11
+ export declare function resolveProjectPath(inputPath: string): string;
12
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAM5D"}
@@ -0,0 +1,22 @@
1
+ import os from 'os';
2
+ import path from 'path';
3
+ /**
4
+ * Returns the Claude Code settings file path, cross-platform.
5
+ * Windows: C:\Users\<user>\.claude\settings.json
6
+ * Unix: ~/.claude/settings.json
7
+ */
8
+ export function getConfigPath() {
9
+ return path.join(os.homedir(), '.claude', 'settings.json');
10
+ }
11
+ /**
12
+ * Returns the project-level plugin config path.
13
+ * Resolves relative to cwd, rejects paths that escape the project root.
14
+ */
15
+ export function resolveProjectPath(inputPath) {
16
+ const resolved = path.resolve(process.cwd(), inputPath);
17
+ if (!resolved.startsWith(process.cwd() + path.sep) && resolved !== process.cwd()) {
18
+ throw new Error(`Path escapes project root: ${inputPath}`);
19
+ }
20
+ return resolved;
21
+ }
22
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,IAAI,MAAM,MAAM,CAAA;AAEvB;;;;GAIG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,eAAe,CAAC,CAAA;AAC5D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAA;IACvD,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC;QACjF,MAAM,IAAI,KAAK,CAAC,8BAA8B,SAAS,EAAE,CAAC,CAAA;IAC5D,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "prompt-caching-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Automatic prompt caching for Claude Code. Cuts token costs by up to 90%.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "prompt-caching-mcp": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsx watch src/index.ts",
13
+ "test": "vitest run",
14
+ "test:watch": "vitest watch",
15
+ "lint": "eslint src/",
16
+ "typecheck": "tsc --noEmit",
17
+ "benchmark": "vitest run src/__tests__/benchmarks/ --passWithNoTests"
18
+ },
19
+ "keywords": [
20
+ "claude",
21
+ "anthropic",
22
+ "mcp",
23
+ "prompt-caching",
24
+ "claude-code",
25
+ "token-optimization",
26
+ "llm",
27
+ "developer-tools",
28
+ "cost-reduction"
29
+ ],
30
+ "author": "",
31
+ "license": "MIT",
32
+ "dependencies": {
33
+ "@anthropic-ai/sdk": "^0.39.0",
34
+ "@modelcontextprotocol/sdk": "^1.8.0"
35
+ },
36
+ "devDependencies": {
37
+ "@commitlint/config-conventional": "^19.0.0",
38
+ "@commitlint/types": "^19.0.0",
39
+ "@eslint/js": "^9.0.0",
40
+ "@semantic-release/changelog": "^6.0.3",
41
+ "@semantic-release/commit-analyzer": "^13.0.1",
42
+ "@semantic-release/git": "^10.0.1",
43
+ "@semantic-release/github": "^12.0.6",
44
+ "@semantic-release/npm": "^13.1.5",
45
+ "@semantic-release/release-notes-generator": "^14.1.0",
46
+ "@types/node": "^22.0.0",
47
+ "@vitest/coverage-v8": "^3.0.0",
48
+ "eslint": "^9.0.0",
49
+ "tsx": "^4.0.0",
50
+ "typescript": "^5.0.0",
51
+ "typescript-eslint": "^8.0.0",
52
+ "vitest": "^3.0.0"
53
+ },
54
+ "engines": {
55
+ "node": ">=24"
56
+ },
57
+ "files": [
58
+ "dist/",
59
+ "README.md",
60
+ "LICENSE"
61
+ ],
62
+ "repository": {
63
+ "type": "git",
64
+ "url": "https://github.com/flightlesstux/prompt-caching.git"
65
+ },
66
+ "bugs": {
67
+ "url": "https://github.com/flightlesstux/prompt-caching/issues"
68
+ },
69
+ "homepage": "https://flightlesstux.github.io/prompt-caching/"
70
+ }