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 +21 -0
- package/README.md +228 -0
- package/dist/api-proxy.d.ts +20 -0
- package/dist/api-proxy.d.ts.map +1 -0
- package/dist/api-proxy.js +37 -0
- package/dist/api-proxy.js.map +1 -0
- package/dist/cache-analyzer.d.ts +51 -0
- package/dist/cache-analyzer.d.ts.map +1 -0
- package/dist/cache-analyzer.js +74 -0
- package/dist/cache-analyzer.js.map +1 -0
- package/dist/handlers.d.ts +17 -0
- package/dist/handlers.d.ts.map +1 -0
- package/dist/handlers.js +62 -0
- package/dist/handlers.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +183 -0
- package/dist/index.js.map +1 -0
- package/dist/prompt-optimizer.d.ts +10 -0
- package/dist/prompt-optimizer.d.ts.map +1 -0
- package/dist/prompt-optimizer.js +73 -0
- package/dist/prompt-optimizer.js.map +1 -0
- package/dist/token-tracker.d.ts +30 -0
- package/dist/token-tracker.d.ts.map +1 -0
- package/dist/token-tracker.js +36 -0
- package/dist/token-tracker.js.map +1 -0
- package/dist/utils/paths.d.ts +12 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +22 -0
- package/dist/utils/paths.js.map +1 -0
- package/package.json +70 -0
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
|
+
[](https://github.com/flightlesstux/prompt-caching/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/prompt-caching-mcp)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
[](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"}
|
package/dist/handlers.js
ADDED
|
@@ -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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAA;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAA;AACnC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAA;AAClG,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,yBAAyB,EACzB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,eAAe,CAAA;AAEtB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAClC,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IACzD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAAwB,CAAA;IACvG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,uBAAuB,GAAG,CAAC,OAAO,IAAI,CAAC,CAAA;IAC5D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC;AAED,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AACzD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAAwB,CAAA;AAEvG,MAAM,MAAM,GAAG,UAAU,EAAE,CAAA;AAC3B,MAAM,OAAO,GAAG,IAAI,YAAY,EAAE,CAAA;AAElC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qCAAqC,aAAa,EAAE,IAAI,CAAC,CAAA;AAC9E,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAA;AAErE,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,EACpD,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAA;AAED,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;IAC5D,KAAK,EAAE;QACL;YACE,IAAI,EAAE,mBAAmB;YACzB,WAAW,EACT,yOAAyO;YAC3O,WAAW,EAAE;gBACX,IAAI,EAAE,QAAiB;gBACvB,UAAU,EAAE;oBACV,QAAQ,EAAE;wBACR,IAAI,EAAE,OAAO;wBACb,WAAW,EAAE,6BAA6B;wBAC1C,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;qBAC1B;oBACD,MAAM,EAAE;wBACN,WAAW,EAAE,0DAA0D;qBACxE;oBACD,KAAK,EAAE;wBACL,IAAI,EAAE,OAAO;wBACb,WAAW,EAAE,kCAAkC;wBAC/C,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;qBAC1B;iBACF;gBACD,QAAQ,EAAE,CAAC,UAAU,CAAC;aACvB;YACD,YAAY,EAAE;gBACZ,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,iBAAiB,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;oBAC/D,eAAe,EAAE,EAAE;oBACnB,cAAc,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;oBAC5D,gBAAgB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBACpC,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBACnC,QAAQ,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;iBACvD;aACF;SACF;QACD;YACE,IAAI,EAAE,iBAAiB;YACvB,WAAW,EACT,uHAAuH;YACzH,WAAW,EAAE;gBACX,IAAI,EAAE,QAAiB;gBACvB,UAAU,EAAE,EAAE;aACf;YACD,YAAY,EAAE;gBACZ,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBACzB,gBAAgB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBACpC,iBAAiB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBACrC,mBAAmB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBACvC,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBACnC,gBAAgB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBACpC,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;iBAC5B;aACF;SACF;QACD;YACE,IAAI,EAAE,mBAAmB;YACzB,WAAW,EAAE,+CAA+C;YAC5D,WAAW,EAAE;gBACX,IAAI,EAAE,QAAiB;gBACvB,UAAU,EAAE,EAAE;aACf;YACD,YAAY,EAAE;gBACZ,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;iBAC3B;aACF;SACF;QACD;YACE,IAAI,EAAE,sBAAsB;YAC5B,WAAW,EACT,uJAAuJ;YACzJ,WAAW,EAAE;gBACX,IAAI,EAAE,QAAiB;gBACvB,UAAU,EAAE;oBACV,QAAQ,EAAE;wBACR,IAAI,EAAE,OAAO;wBACb,WAAW,EAAE,4BAA4B;wBACzC,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;qBAC1B;oBACD,MAAM,EAAE;wBACN,WAAW,EAAE,0DAA0D;qBACxE;oBACD,KAAK,EAAE;wBACL,IAAI,EAAE,OAAO;wBACb,WAAW,EAAE,kCAAkC;wBAC/C,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;qBAC1B;iBACF;gBACD,QAAQ,EAAE,CAAC,UAAU,CAAC;aACvB;YACD,YAAY,EAAE;gBACZ,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,QAAQ,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;oBACtD,oBAAoB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBACxC,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBACnC,sBAAsB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;iBAC3C;aACF;SACF;QACD;YACE,IAAI,EAAE,cAAc;YACpB,WAAW,EACT,0LAA0L;YAC5L,WAAW,EAAE;gBACX,IAAI,EAAE,QAAiB;gBACvB,UAAU,EAAE;oBACV,YAAY,EAAmB,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,4BAA4B,EAAE;oBAC5F,aAAa,EAAkB,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,6BAA6B,EAAE;oBAC7F,2BAA2B,EAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,wCAAwC,EAAE;oBACxG,uBAAuB,EAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,sCAAsC,EAAE;iBACvG;aACF;YACD,YAAY,EAAE;gBACZ,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;oBAC7B,YAAY,EAAE;wBACZ,IAAI,EAAE,QAAQ;wBACd,UAAU,EAAE;4BACV,KAAK,EAAiB,EAAE,IAAI,EAAE,QAAQ,EAAE;4BACxC,gBAAgB,EAAM,EAAE,IAAI,EAAE,QAAQ,EAAE;4BACxC,iBAAiB,EAAK,EAAE,IAAI,EAAE,QAAQ,EAAE;4BACxC,mBAAmB,EAAG,EAAE,IAAI,EAAE,QAAQ,EAAE;4BACxC,eAAe,EAAO,EAAE,IAAI,EAAE,QAAQ,EAAE;4BACxC,gBAAgB,EAAM,EAAE,IAAI,EAAE,QAAQ,EAAE;4BACxC,OAAO,EAAe,EAAE,IAAI,EAAE,QAAQ,EAAE;yBACzC;qBACF;iBACF;aACF;SACF;KACF;CACF,CAAC,CAAC,CAAA;AAEH,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;IAChE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,MAAM,CAAA;IACpD,IAAI,CAAC;QACH,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,mBAAmB,CAAC,CAAI,OAAO,sBAAsB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;YAC5E,KAAK,iBAAiB,CAAC,CAAM,OAAO,mBAAmB,CAAC,OAAO,CAAC,CAAA;YAChE,KAAK,mBAAmB,CAAC,CAAI,OAAO,qBAAqB,CAAC,OAAO,CAAC,CAAA;YAClE,KAAK,sBAAsB,CAAC,CAAC,OAAO,yBAAyB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;YAC/E,KAAK,cAAc,CAAC,CAAS,OAAO,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;YACxE,OAAO,CAAC,CAAqB,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAA;QAC7D,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;SAChG,CAAA;IACH,CAAC;AACH,CAAC,CAAC,CAAA;AAEF,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;AAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;AAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAA"}
|
|
@@ -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
|
+
}
|