bunnycdn-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 +294 -0
- package/index.js +123 -0
- package/lib/helpers.js +87 -0
- package/lib/tools/account.js +177 -0
- package/lib/tools/dns-zones.js +181 -0
- package/lib/tools/edge-scripting.js +219 -0
- package/lib/tools/magic-containers.js +208 -0
- package/lib/tools/origin-errors.js +34 -0
- package/lib/tools/pull-zones.js +209 -0
- package/lib/tools/shield.js +249 -0
- package/lib/tools/storage-files.js +91 -0
- package/lib/tools/storage-zones.js +113 -0
- package/lib/tools/stream-collections.js +99 -0
- package/lib/tools/stream-libraries.js +106 -0
- package/lib/tools/stream-videos.js +231 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 anvme
|
|
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,294 @@
|
|
|
1
|
+
# bunnycdn-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for [BunnyCDN](https://bunny.net) — manage pull zones, DNS, storage, video streaming, edge scripting, security, and Magic Containers right from your AI assistant.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
| Tool | Description | Data Source |
|
|
8
|
+
|------|-------------|-------------|
|
|
9
|
+
| `bunny_get_account` | Get account details and balance | bunny.net API |
|
|
10
|
+
| `bunny_get_billing_summary` | Get billing summary with charges | bunny.net API |
|
|
11
|
+
| `bunny_get_statistics` | Get CDN statistics (bandwidth, requests, cache hit rate) | bunny.net API |
|
|
12
|
+
| `bunny_global_search` | Search across all resources | bunny.net API |
|
|
13
|
+
| `bunny_purge_url` | Purge a URL from CDN cache | bunny.net API |
|
|
14
|
+
| `bunny_list_regions` | List CDN edge regions | bunny.net API |
|
|
15
|
+
| `bunny_list_countries` | List countries for geo-blocking | bunny.net API |
|
|
16
|
+
| `bunny_list_pull_zones` | List pull zones with search and pagination | bunny.net API |
|
|
17
|
+
| `bunny_get_pull_zone` | Get pull zone details | bunny.net API |
|
|
18
|
+
| `bunny_create_pull_zone` | Create a pull zone | bunny.net API |
|
|
19
|
+
| `bunny_update_pull_zone` | Update pull zone settings | bunny.net API |
|
|
20
|
+
| `bunny_delete_pull_zone` | Delete a pull zone | bunny.net API |
|
|
21
|
+
| `bunny_purge_pull_zone_cache` | Purge entire pull zone cache | bunny.net API |
|
|
22
|
+
| `bunny_manage_pull_zone_hostnames` | Add or remove custom hostnames | bunny.net API |
|
|
23
|
+
| `bunny_manage_edge_rules` | Add, update, delete, or toggle edge rules | bunny.net API |
|
|
24
|
+
| `bunny_list_dns_zones` | List DNS zones | bunny.net API |
|
|
25
|
+
| `bunny_get_dns_zone` | Get DNS zone with all records | bunny.net API |
|
|
26
|
+
| `bunny_create_dns_zone` | Create a DNS zone | bunny.net API |
|
|
27
|
+
| `bunny_update_dns_zone` | Update DNS zone settings | bunny.net API |
|
|
28
|
+
| `bunny_delete_dns_zone` | Delete a DNS zone | bunny.net API |
|
|
29
|
+
| `bunny_manage_dns_record` | Add, update, or delete DNS records | bunny.net API |
|
|
30
|
+
| `bunny_get_dns_statistics` | Get DNS query statistics | bunny.net API |
|
|
31
|
+
| `bunny_list_storage_zones` | List storage zones | bunny.net API |
|
|
32
|
+
| `bunny_get_storage_zone` | Get storage zone details | bunny.net API |
|
|
33
|
+
| `bunny_create_storage_zone` | Create a storage zone | bunny.net API |
|
|
34
|
+
| `bunny_get_storage_zone_statistics` | Get storage zone usage statistics | bunny.net API |
|
|
35
|
+
| `bunny_list_storage_files` | List files and directories | Storage API |
|
|
36
|
+
| `bunny_download_storage_file` | Download file content | Storage API |
|
|
37
|
+
| `bunny_delete_storage_file` | Delete a file or directory | Storage API |
|
|
38
|
+
| `bunny_list_video_libraries` | List video libraries | bunny.net API |
|
|
39
|
+
| `bunny_get_video_library` | Get library details | bunny.net API |
|
|
40
|
+
| `bunny_create_video_library` | Create a video library | bunny.net API |
|
|
41
|
+
| `bunny_update_video_library` | Update library settings | bunny.net API |
|
|
42
|
+
| `bunny_list_videos` | List videos with search and pagination | Stream API |
|
|
43
|
+
| `bunny_get_video` | Get video details | Stream API |
|
|
44
|
+
| `bunny_create_video` | Create video object, optionally fetch from URL | Stream API |
|
|
45
|
+
| `bunny_update_video` | Update video metadata | Stream API |
|
|
46
|
+
| `bunny_delete_video` | Delete a video | Stream API |
|
|
47
|
+
| `bunny_get_video_statistics` | Get view statistics | Stream API |
|
|
48
|
+
| `bunny_get_video_heatmap` | Get attention heatmap data | Stream API |
|
|
49
|
+
| `bunny_reencode_video` | Re-encode a video | Stream API |
|
|
50
|
+
| `bunny_list_collections` | List video collections | Stream API |
|
|
51
|
+
| `bunny_get_collection` | Get collection details | Stream API |
|
|
52
|
+
| `bunny_manage_collection` | Create, update, or delete collections | Stream API |
|
|
53
|
+
| `bunny_list_edge_scripts` | List edge scripts | bunny.net API |
|
|
54
|
+
| `bunny_get_edge_script` | Get script details | bunny.net API |
|
|
55
|
+
| `bunny_get_edge_script_code` | Get script source code | bunny.net API |
|
|
56
|
+
| `bunny_set_edge_script_code` | Upload script code (saved as draft) | bunny.net API |
|
|
57
|
+
| `bunny_manage_edge_script` | Create, update, or delete scripts | bunny.net API |
|
|
58
|
+
| `bunny_publish_edge_script` | Publish a release to edge servers | bunny.net API |
|
|
59
|
+
| `bunny_manage_edge_script_variables` | Manage environment variables and secrets | bunny.net API |
|
|
60
|
+
| `bunny_list_shield_zones` | List shield security zones | bunny.net API |
|
|
61
|
+
| `bunny_get_shield_zone` | Get zone by shield zone ID or pull zone ID | bunny.net API |
|
|
62
|
+
| `bunny_get_waf_rules` | Get WAF rules and profiles | bunny.net API |
|
|
63
|
+
| `bunny_manage_waf_custom_rule` | Create, update, or delete custom WAF rules | bunny.net API |
|
|
64
|
+
| `bunny_list_rate_limit_rules` | List rate limiting rules | bunny.net API |
|
|
65
|
+
| `bunny_manage_rate_limit_rule` | Create, update, or delete rate limit rules | bunny.net API |
|
|
66
|
+
| `bunny_get_shield_metrics` | Get security metrics overview | bunny.net API |
|
|
67
|
+
| `bunny_get_bot_detection` | Get or update bot detection settings | bunny.net API |
|
|
68
|
+
| `bunny_list_mc_apps` | List Magic Container applications | bunny.net API |
|
|
69
|
+
| `bunny_get_mc_app` | Get application details | bunny.net API |
|
|
70
|
+
| `bunny_get_mc_app_overview` | Get app overview with real-time metrics | bunny.net API |
|
|
71
|
+
| `bunny_manage_mc_app` | Create, update, or delete applications | bunny.net API |
|
|
72
|
+
| `bunny_mc_app_lifecycle` | Deploy, undeploy, or restart applications | bunny.net API |
|
|
73
|
+
| `bunny_list_mc_registries` | List container registries | bunny.net API |
|
|
74
|
+
| `bunny_list_mc_regions` | List deployment regions | bunny.net API |
|
|
75
|
+
| `bunny_get_mc_app_statistics` | Get application statistics | bunny.net API |
|
|
76
|
+
| `bunny_get_origin_errors` | Get origin error logs for a pull zone | bunny.net API |
|
|
77
|
+
|
|
78
|
+
**Data sources:** Tools marked **Storage API** require `BUNNY_STORAGE_KEY`. Tools marked **Stream API** require `BUNNY_STREAM_KEY`. All other tools use `BUNNY_API_KEY`.
|
|
79
|
+
|
|
80
|
+
## Prerequisites
|
|
81
|
+
|
|
82
|
+
- Node.js >= 18
|
|
83
|
+
- A [bunny.net](https://bunny.net) account with an API key
|
|
84
|
+
- Optional: Stream library API key (for video tools)
|
|
85
|
+
- Optional: Storage zone password (for file tools)
|
|
86
|
+
|
|
87
|
+
## Setup
|
|
88
|
+
|
|
89
|
+
No installation needed — just configure your MCP client:
|
|
90
|
+
|
|
91
|
+
<details>
|
|
92
|
+
<summary>VS Code / Copilot</summary>
|
|
93
|
+
|
|
94
|
+
Add to `.vscode/mcp.json`:
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"servers": {
|
|
98
|
+
"bunnycdn": {
|
|
99
|
+
"command": "npx",
|
|
100
|
+
"args": ["-y", "bunnycdn-mcp"],
|
|
101
|
+
"env": {
|
|
102
|
+
"BUNNY_API_KEY": "your-api-key"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
</details>
|
|
109
|
+
|
|
110
|
+
<details>
|
|
111
|
+
<summary>Cursor</summary>
|
|
112
|
+
|
|
113
|
+
Add to `.cursor/mcp.json`:
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"mcpServers": {
|
|
117
|
+
"bunnycdn": {
|
|
118
|
+
"command": "npx",
|
|
119
|
+
"args": ["-y", "bunnycdn-mcp"],
|
|
120
|
+
"env": {
|
|
121
|
+
"BUNNY_API_KEY": "your-api-key"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
</details>
|
|
128
|
+
|
|
129
|
+
<details>
|
|
130
|
+
<summary>Windsurf</summary>
|
|
131
|
+
|
|
132
|
+
Add to `~/.codeium/windsurf/mcp_config.json`:
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"mcpServers": {
|
|
136
|
+
"bunnycdn": {
|
|
137
|
+
"command": "npx",
|
|
138
|
+
"args": ["-y", "bunnycdn-mcp"],
|
|
139
|
+
"env": {
|
|
140
|
+
"BUNNY_API_KEY": "your-api-key"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
</details>
|
|
147
|
+
|
|
148
|
+
<details>
|
|
149
|
+
<summary>Claude Desktop</summary>
|
|
150
|
+
|
|
151
|
+
Add to `claude_desktop_config.json`:
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"mcpServers": {
|
|
155
|
+
"bunnycdn": {
|
|
156
|
+
"command": "npx",
|
|
157
|
+
"args": ["-y", "bunnycdn-mcp"],
|
|
158
|
+
"env": {
|
|
159
|
+
"BUNNY_API_KEY": "your-api-key"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
</details>
|
|
166
|
+
|
|
167
|
+
<details>
|
|
168
|
+
<summary>Claude Code</summary>
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
claude mcp add --transport stdio bunnycdn -- npx -y bunnycdn-mcp
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Or add to `.mcp.json` (shared with team):
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"mcpServers": {
|
|
178
|
+
"bunnycdn": {
|
|
179
|
+
"command": "npx",
|
|
180
|
+
"args": ["-y", "bunnycdn-mcp"],
|
|
181
|
+
"env": {
|
|
182
|
+
"BUNNY_API_KEY": "your-api-key"
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
</details>
|
|
189
|
+
|
|
190
|
+
<details>
|
|
191
|
+
<summary>Zed</summary>
|
|
192
|
+
|
|
193
|
+
Add to `settings.json`:
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"context_servers": {
|
|
197
|
+
"bunnycdn": {
|
|
198
|
+
"command": "npx",
|
|
199
|
+
"args": ["-y", "bunnycdn-mcp"],
|
|
200
|
+
"env": {
|
|
201
|
+
"BUNNY_API_KEY": "your-api-key"
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
</details>
|
|
208
|
+
|
|
209
|
+
<details>
|
|
210
|
+
<summary>JetBrains IDEs</summary>
|
|
211
|
+
|
|
212
|
+
Open **Settings → Tools → AI Assistant → MCP**, click **+**, and paste:
|
|
213
|
+
```json
|
|
214
|
+
{
|
|
215
|
+
"mcpServers": {
|
|
216
|
+
"bunnycdn": {
|
|
217
|
+
"command": "npx",
|
|
218
|
+
"args": ["-y", "bunnycdn-mcp"],
|
|
219
|
+
"env": {
|
|
220
|
+
"BUNNY_API_KEY": "your-api-key"
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
</details>
|
|
227
|
+
|
|
228
|
+
<details>
|
|
229
|
+
<summary>Gemini CLI</summary>
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
gemini mcp add bunnycdn -- npx -y bunnycdn-mcp
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Or add to `~/.gemini/settings.json`:
|
|
236
|
+
```json
|
|
237
|
+
{
|
|
238
|
+
"mcpServers": {
|
|
239
|
+
"bunnycdn": {
|
|
240
|
+
"command": "npx",
|
|
241
|
+
"args": ["-y", "bunnycdn-mcp"],
|
|
242
|
+
"env": {
|
|
243
|
+
"BUNNY_API_KEY": "your-api-key"
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
</details>
|
|
250
|
+
|
|
251
|
+
<details>
|
|
252
|
+
<summary>Other MCP clients</summary>
|
|
253
|
+
|
|
254
|
+
Any MCP client that supports **stdio** transport can use this server. The command is:
|
|
255
|
+
```
|
|
256
|
+
npx -y bunnycdn-mcp
|
|
257
|
+
```
|
|
258
|
+
See the [full list of MCP clients](https://modelcontextprotocol.io/clients).
|
|
259
|
+
</details>
|
|
260
|
+
|
|
261
|
+
### Optional environment variables
|
|
262
|
+
|
|
263
|
+
| Variable | Description |
|
|
264
|
+
|----------|-------------|
|
|
265
|
+
| `BUNNY_STREAM_KEY` | Video library API key — enables Stream video and collection tools |
|
|
266
|
+
| `BUNNY_STORAGE_KEY` | Storage zone password — enables Storage file tools |
|
|
267
|
+
| `BUNNY_STORAGE_REGION` | Storage region code (default: empty for Falkenstein) |
|
|
268
|
+
| `BUNNY_STORAGE_ZONE` | Default storage zone name |
|
|
269
|
+
|
|
270
|
+
Add these to the `env` block in your MCP client configuration above.
|
|
271
|
+
|
|
272
|
+
### Local development
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
git clone https://github.com/anvme/bunnycdn-mcp.git
|
|
276
|
+
cd bunnycdn-mcp
|
|
277
|
+
npm install
|
|
278
|
+
npm test
|
|
279
|
+
node index.js
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## How It Works
|
|
283
|
+
|
|
284
|
+
This MCP server connects to the [bunny.net API](https://docs.bunny.net/reference/bunnynet-api-overview) using your API key. It registers up to 68 tools depending on which API keys are provided:
|
|
285
|
+
|
|
286
|
+
- **Core tools** (55 tools) — always available with `BUNNY_API_KEY`
|
|
287
|
+
- **Stream tools** (11 tools) — registered when `BUNNY_STREAM_KEY` is set
|
|
288
|
+
- **Storage file tools** (3 tools) — registered when `BUNNY_STORAGE_KEY` is set
|
|
289
|
+
|
|
290
|
+
All read operations are cached in-memory with a short TTL for performance. Every tool includes MCP annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`) so clients can make informed decisions about tool approval.
|
|
291
|
+
|
|
292
|
+
## License
|
|
293
|
+
|
|
294
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import axios from "axios";
|
|
6
|
+
import { readFileSync } from "fs";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { dirname, join } from "path";
|
|
9
|
+
import { createCache } from "./lib/helpers.js";
|
|
10
|
+
import { registerAccountTools } from "./lib/tools/account.js";
|
|
11
|
+
import { registerPullZoneTools } from "./lib/tools/pull-zones.js";
|
|
12
|
+
import { registerDnsTools } from "./lib/tools/dns-zones.js";
|
|
13
|
+
import { registerStorageZoneTools } from "./lib/tools/storage-zones.js";
|
|
14
|
+
import { registerStorageFileTools } from "./lib/tools/storage-files.js";
|
|
15
|
+
import { registerStreamLibraryTools } from "./lib/tools/stream-libraries.js";
|
|
16
|
+
import { registerStreamVideoTools } from "./lib/tools/stream-videos.js";
|
|
17
|
+
import { registerStreamCollectionTools } from "./lib/tools/stream-collections.js";
|
|
18
|
+
import { registerEdgeScriptingTools } from "./lib/tools/edge-scripting.js";
|
|
19
|
+
import { registerShieldTools } from "./lib/tools/shield.js";
|
|
20
|
+
import { registerMagicContainerTools } from "./lib/tools/magic-containers.js";
|
|
21
|
+
import { registerOriginErrorTools } from "./lib/tools/origin-errors.js";
|
|
22
|
+
|
|
23
|
+
// ─── Setup ───────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
process.on("uncaughtException", (err) => {
|
|
26
|
+
process.stderr.write(`Uncaught exception: ${err.message}\n`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
});
|
|
29
|
+
process.on("unhandledRejection", (reason) => {
|
|
30
|
+
process.stderr.write(`Unhandled rejection: ${reason}\n`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "package.json"), "utf-8"));
|
|
36
|
+
|
|
37
|
+
// ─── Validate API Key ────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
const BUNNY_API_KEY = process.env.BUNNY_API_KEY;
|
|
40
|
+
if (!BUNNY_API_KEY) {
|
|
41
|
+
process.stderr.write("Fatal: BUNNY_API_KEY environment variable is required\n");
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── HTTP Clients ────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
const ua = `${pkg.name}/${pkg.version}`;
|
|
48
|
+
|
|
49
|
+
const coreHttp = axios.create({
|
|
50
|
+
baseURL: "https://api.bunny.net",
|
|
51
|
+
timeout: 15_000,
|
|
52
|
+
maxContentLength: 5 * 1024 * 1024,
|
|
53
|
+
headers: { "User-Agent": ua, "AccessKey": BUNNY_API_KEY, "Accept": "application/json" },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const originHttp = axios.create({
|
|
57
|
+
baseURL: "https://cdn-origin-logging.bunny.net",
|
|
58
|
+
timeout: 15_000,
|
|
59
|
+
maxContentLength: 5 * 1024 * 1024,
|
|
60
|
+
headers: { "User-Agent": ua, "AccessKey": BUNNY_API_KEY, "Accept": "application/json" },
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const BUNNY_STREAM_KEY = process.env.BUNNY_STREAM_KEY;
|
|
64
|
+
const streamHttp = BUNNY_STREAM_KEY ? axios.create({
|
|
65
|
+
baseURL: "https://video.bunnycdn.com",
|
|
66
|
+
timeout: 15_000,
|
|
67
|
+
maxContentLength: 5 * 1024 * 1024,
|
|
68
|
+
headers: { "User-Agent": ua, "AccessKey": BUNNY_STREAM_KEY, "Accept": "application/json" },
|
|
69
|
+
}) : null;
|
|
70
|
+
|
|
71
|
+
const BUNNY_STORAGE_KEY = process.env.BUNNY_STORAGE_KEY;
|
|
72
|
+
const storageRegion = process.env.BUNNY_STORAGE_REGION || "";
|
|
73
|
+
const storageBase = storageRegion
|
|
74
|
+
? `https://${storageRegion}.storage.bunnycdn.com`
|
|
75
|
+
: "https://storage.bunnycdn.com";
|
|
76
|
+
const storageHttp = BUNNY_STORAGE_KEY ? axios.create({
|
|
77
|
+
baseURL: storageBase,
|
|
78
|
+
timeout: 30_000,
|
|
79
|
+
maxContentLength: 10 * 1024 * 1024,
|
|
80
|
+
headers: { "User-Agent": ua, "AccessKey": BUNNY_STORAGE_KEY, "Accept": "*/*" },
|
|
81
|
+
}) : null;
|
|
82
|
+
|
|
83
|
+
// ─── Cache ───────────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
const cache = createCache({ ttl: 3 * 60 * 1000, max: 300 });
|
|
86
|
+
|
|
87
|
+
// ─── Server ──────────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
const server = new McpServer({
|
|
90
|
+
name: pkg.name,
|
|
91
|
+
description: pkg.description,
|
|
92
|
+
version: pkg.version,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// ─── Register Tools ──────────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
registerAccountTools(server, coreHttp, cache);
|
|
98
|
+
registerPullZoneTools(server, coreHttp, cache);
|
|
99
|
+
registerDnsTools(server, coreHttp, cache);
|
|
100
|
+
registerStorageZoneTools(server, coreHttp, cache);
|
|
101
|
+
registerStreamLibraryTools(server, coreHttp, cache);
|
|
102
|
+
registerEdgeScriptingTools(server, coreHttp, cache);
|
|
103
|
+
registerShieldTools(server, coreHttp, cache);
|
|
104
|
+
registerMagicContainerTools(server, coreHttp, cache);
|
|
105
|
+
registerOriginErrorTools(server, originHttp, cache);
|
|
106
|
+
|
|
107
|
+
if (streamHttp) {
|
|
108
|
+
registerStreamVideoTools(server, streamHttp, cache);
|
|
109
|
+
registerStreamCollectionTools(server, streamHttp, cache);
|
|
110
|
+
}
|
|
111
|
+
if (storageHttp) {
|
|
112
|
+
registerStorageFileTools(server, storageHttp, cache);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─── Start ───────────────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const transport = new StdioServerTransport();
|
|
119
|
+
await server.connect(transport);
|
|
120
|
+
} catch (err) {
|
|
121
|
+
process.stderr.write(`Fatal: failed to start server: ${err.message}\n`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
package/lib/helpers.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper functions for bunnycdn-mcp server.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
// ─── Cache ─────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
const DEFAULT_TTL = 3 * 60 * 1000;
|
|
10
|
+
const MAX_ENTRIES = 300;
|
|
11
|
+
|
|
12
|
+
export function createCache(opts = {}) {
|
|
13
|
+
const ttl = opts.ttl ?? DEFAULT_TTL;
|
|
14
|
+
const max = opts.max ?? MAX_ENTRIES;
|
|
15
|
+
const store = new Map();
|
|
16
|
+
return {
|
|
17
|
+
get(key) {
|
|
18
|
+
const entry = store.get(key);
|
|
19
|
+
if (!entry) return undefined;
|
|
20
|
+
if (Date.now() - entry.ts > ttl) { store.delete(key); return undefined; }
|
|
21
|
+
return entry.data;
|
|
22
|
+
},
|
|
23
|
+
set(key, data) {
|
|
24
|
+
if (store.size >= max) {
|
|
25
|
+
const oldest = store.keys().next().value;
|
|
26
|
+
store.delete(oldest);
|
|
27
|
+
}
|
|
28
|
+
store.set(key, { data, ts: Date.now() });
|
|
29
|
+
},
|
|
30
|
+
get size() { return store.size; },
|
|
31
|
+
clear() { store.clear(); },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ─── Query String ──────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
export function buildQueryString(params) {
|
|
38
|
+
const entries = Object.entries(params).filter(([, v]) => v !== undefined && v !== null);
|
|
39
|
+
if (entries.length === 0) return "";
|
|
40
|
+
return "?" + entries.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── Response Formatters ───────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
export function formatResponse(data) {
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function formatError(msg) {
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: "text", text: msg }],
|
|
54
|
+
isError: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function handleToolError(err) {
|
|
59
|
+
if (err.response) {
|
|
60
|
+
const body = err.response.data;
|
|
61
|
+
const detail = body?.Message ?? body?.ErrorKey ?? "";
|
|
62
|
+
return formatError(`BunnyCDN API error: ${err.response.status}${detail ? ` — ${detail}` : ""}`);
|
|
63
|
+
}
|
|
64
|
+
return formatError(`Network error: ${err.message}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── Reusable Zod Parameters ───────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
export function pageParam() {
|
|
70
|
+
return z.number().min(1).optional().describe("Page number. Default: 1");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function perPageParam() {
|
|
74
|
+
return z.number().min(1).max(1000).optional().describe("Items per page. Default: 1000");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function dateFromParam() {
|
|
78
|
+
return z.string().optional().describe("Start date (YYYY-MM-DD or ISO 8601)");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function dateToParam() {
|
|
82
|
+
return z.string().optional().describe("End date (YYYY-MM-DD or ISO 8601)");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function idParam(label) {
|
|
86
|
+
return z.number().int().positive().describe(`${label} ID`);
|
|
87
|
+
}
|