deploy-mcp 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,7 @@ Track deployments across all platforms directly in your AI conversation.
8
8
  No more context-switching. No more dashboard hunting.
9
9
 
10
10
  [![npm version](https://img.shields.io/npm/v/deploy-mcp.svg?style=for-the-badge&color=ff6b6b)](https://www.npmjs.com/package/deploy-mcp)
11
+ [![npm downloads](https://img.shields.io/npm/dm/deploy-mcp.svg?style=for-the-badge&color=00d2d3)](https://www.npmjs.com/package/deploy-mcp)
11
12
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=for-the-badge)](https://github.com/alexpota/deploy-mcp/blob/main/LICENSE)
12
13
  [![Website](https://img.shields.io/badge/website-deploy--mcp.io-ff8e53?style=for-the-badge)](https://deploy-mcp.io)
13
14
 
@@ -16,34 +17,22 @@ No more context-switching. No more dashboard hunting.
16
17
  ---
17
18
 
18
19
  ## Demo
19
- ─────────────────────────
20
20
 
21
- ### Your conversation:
22
- ```
23
- User: "Check my latest Vercel deployment"
24
- ```
21
+ <div align="center">
22
+
23
+ <kbd>
24
+ <a href="./assets/demo.gif">
25
+ <img src="./assets/demo-preview.png" alt="Click to watch full demo" width="600" />
26
+ </a>
27
+ </kbd>
28
+
29
+ <br /><br />
30
+
31
+ <sub>**[Watch Full Demo](./assets/demo.gif)** · **[Live Badge Demo](https://mcp-netlify-demo.netlify.app/)**</sub>
32
+
33
+ </div>
25
34
 
26
- ### AI Assistant's Response:
27
-
28
- > ## Deployment Status
29
- >
30
- > ### Current Status
31
- > **Project:** my-app
32
- > **Platform:** Vercel
33
- > **Status:** ✅ Success
34
- > **URL:** https://my-app.vercel.app
35
- > **Duration:** 45s
36
- > **Deployed:** 2 hours ago
37
- >
38
- > ### Commit Info
39
- > **SHA:** `abc123ef`
40
- > **Message:** Update homepage hero section
41
- > **Author:** John Doe
42
- >
43
- > Everything looks good - your deployment is live and running successfully!
44
-
45
- **Just ask your AI**: *"What's the status of my latest deployment?"*
46
- Get instant answers without leaving your conversation.
35
+ ---
47
36
 
48
37
  ## Quick Start
49
38
  ─────────────────────────
@@ -65,8 +54,8 @@ deploy-mcp supports multiple deployment platforms simultaneously. Configure as m
65
54
  |----------|--------|---------------|----------|
66
55
  | **Vercel** | ✅ Ready | `VERCEL_TOKEN` | Status, Logs, History, Real-time Monitoring |
67
56
  | **Netlify** | ✅ Ready | `NETLIFY_TOKEN` | Status, Logs, History, Real-time Monitoring |
68
- | **Railway** | Coming Soon | `RAILWAY_TOKEN` | - |
69
- | **Render** | Coming Soon | `RENDER_TOKEN` | - |
57
+ | **Cloudflare Pages** | Ready | `CLOUDFLARE_TOKEN` | Status, Logs, History, Real-time Monitoring |
58
+ | **GitHub Pages** | 🚧 Coming Soon | `GITHUB_TOKEN` | - |
70
59
 
71
60
  ### Multi-Platform Configuration
72
61
 
@@ -80,7 +69,8 @@ You can use **multiple platforms simultaneously** by providing tokens for each p
80
69
  "args": ["-y", "deploy-mcp"],
81
70
  "env": {
82
71
  "VERCEL_TOKEN": "your-vercel-token",
83
- "NETLIFY_TOKEN": "your-netlify-token"
72
+ "NETLIFY_TOKEN": "your-netlify-token",
73
+ "CLOUDFLARE_TOKEN": "accountId:globalApiKey"
84
74
  // Add more platform tokens as needed
85
75
  }
86
76
  }
@@ -155,6 +145,46 @@ You can use **multiple platforms simultaneously** by providing tokens for each p
155
145
 
156
146
  </details>
157
147
 
148
+ ### Cloudflare Pages
149
+
150
+ <details>
151
+ <summary><strong>Setup Instructions</strong></summary>
152
+
153
+ 1. **Get your API token:**
154
+ - Go to [dash.cloudflare.com/profile/api-tokens](https://dash.cloudflare.com/profile/api-tokens)
155
+ - Click "Create Token"
156
+ - Use "Custom token" with these permissions:
157
+ - **Zone:Zone:Read**
158
+ - **Zone:Page Rules:Read**
159
+ - **Account:Cloudflare Pages:Edit**
160
+ - Or use your Global API Key (format: `accountId:globalApiKey`)
161
+ - Copy the token
162
+
163
+ 2. **Add to your AI assistant configuration:**
164
+ ```json
165
+ {
166
+ "env": {
167
+ "CLOUDFLARE_TOKEN": "your-cloudflare-token-or-accountId:globalApiKey"
168
+ }
169
+ }
170
+ ```
171
+
172
+ 3. **Available commands:**
173
+ - `"Check my Cloudflare Pages deployment for project-name"`
174
+ - `"What's the status of my latest Cloudflare Pages deployment?"`
175
+ - `"Show me Cloudflare Pages deployment logs"`
176
+ - `"Watch my Cloudflare Pages deployment progress"`
177
+ - `"List all my Cloudflare Pages projects"`
178
+ - `"Show deployment history for project-name"`
179
+
180
+ 4. **Token formats supported:**
181
+ - **API Token**: `your-api-token` (requires `CLOUDFLARE_ACCOUNT_ID` env var)
182
+ - **Global API Key**: `accountId:globalApiKey` (all-in-one format)
183
+
184
+ 5. **Required permissions:** Account access to Cloudflare Pages
185
+
186
+ </details>
187
+
158
188
  ## AI Assistant Configuration
159
189
  ─────────────────────────
160
190
 
@@ -1,6 +1,6 @@
1
1
  // src/core/tools.ts
2
2
  import { z } from "zod";
3
- var SUPPORTED_PLATFORMS = ["vercel", "netlify"];
3
+ var SUPPORTED_PLATFORMS = ["vercel", "netlify", "cloudflare-pages"];
4
4
  var checkDeploymentStatusSchema = z.object({
5
5
  platform: z.enum(SUPPORTED_PLATFORMS).describe("The deployment platform"),
6
6
  project: z.string().describe("The project name or ID"),
@@ -57,7 +57,7 @@ var tools = [
57
57
  properties: {
58
58
  platform: {
59
59
  type: "string",
60
- enum: ["vercel", "netlify"],
60
+ enum: SUPPORTED_PLATFORMS,
61
61
  description: "The deployment platform"
62
62
  },
63
63
  project: {
@@ -87,7 +87,7 @@ var tools = [
87
87
  properties: {
88
88
  platform: {
89
89
  type: "string",
90
- enum: ["vercel", "netlify"],
90
+ enum: SUPPORTED_PLATFORMS,
91
91
  description: "The deployment platform"
92
92
  },
93
93
  project: {
@@ -114,7 +114,7 @@ var tools = [
114
114
  properties: {
115
115
  platform: {
116
116
  type: "string",
117
- enum: ["vercel", "netlify"],
117
+ enum: SUPPORTED_PLATFORMS,
118
118
  description: "The deployment platform"
119
119
  },
120
120
  project: {
@@ -165,7 +165,7 @@ var tools = [
165
165
  properties: {
166
166
  platform: {
167
167
  type: "string",
168
- enum: ["vercel", "netlify"],
168
+ enum: SUPPORTED_PLATFORMS,
169
169
  description: "The deployment platform"
170
170
  },
171
171
  deploymentId: {
@@ -198,7 +198,7 @@ var tools = [
198
198
  properties: {
199
199
  platform: {
200
200
  type: "string",
201
- enum: ["vercel", "netlify"],
201
+ enum: SUPPORTED_PLATFORMS,
202
202
  description: "The deployment platform"
203
203
  },
204
204
  limit: {
@@ -351,6 +351,7 @@ var API_PARAMS = {
351
351
  };
352
352
  var API_CONFIG = {
353
353
  VERCEL_BASE_URL: "https://api.vercel.com",
354
+ CLOUDFLARE_BASE_URL: "https://api.cloudflare.com/client/v4",
354
355
  DEFAULT_TIMEOUT_MS: 1e4,
355
356
  DEFAULT_RETRY_ATTEMPTS: 3,
356
357
  DEFAULT_DEPLOYMENT_LIMIT: 10,
@@ -359,8 +360,8 @@ var API_CONFIG = {
359
360
  var PLATFORM = {
360
361
  VERCEL: "vercel",
361
362
  NETLIFY: "netlify",
362
- RAILWAY: "railway",
363
- RENDER: "render"
363
+ CLOUDFLARE_PAGES: "cloudflare-pages",
364
+ GITHUB_PAGES: "github-pages"
364
365
  };
365
366
  var ENVIRONMENT_TYPES = {
366
367
  PRODUCTION: "production",
@@ -392,10 +393,21 @@ var NETLIFY_STATES = {
392
393
  ERROR: "error",
393
394
  RETRYING: "retrying"
394
395
  };
396
+ var CLOUDFLARE_PAGES_STATES = {
397
+ ACTIVE: "active",
398
+ SUCCESS: "success",
399
+ FAILED: "failed",
400
+ CANCELED: "canceled",
401
+ SKIPPED: "skipped"
402
+ };
395
403
  var ADAPTER_ERRORS = {
396
- TOKEN_REQUIRED: "Vercel token required. Set VERCEL_TOKEN environment variable or pass token parameter.",
397
- FETCH_DEPLOYMENT_FAILED: "Failed to fetch deployment status from Vercel",
398
- UNKNOWN_STATUS: "unknown"
404
+ TOKEN_REQUIRED: "API token required. Set appropriate environment variable or pass token parameter.",
405
+ VERCEL_TOKEN_REQUIRED: "Vercel token required. Set VERCEL_TOKEN environment variable or pass token parameter.",
406
+ NETLIFY_TOKEN_REQUIRED: "Netlify token required. Set NETLIFY_TOKEN environment variable or pass token parameter.",
407
+ CLOUDFLARE_TOKEN_REQUIRED: "Cloudflare token required. Set CLOUDFLARE_TOKEN environment variable or pass token parameter.",
408
+ FETCH_DEPLOYMENT_FAILED: "Failed to fetch deployment status",
409
+ UNKNOWN_STATUS: "unknown",
410
+ CLOUDFLARE_ACCOUNT_ID_REQUIRED: "Cloudflare account ID required. Provide as 'accountId:apiToken' or set CLOUDFLARE_ACCOUNT_ID"
399
411
  };
400
412
 
401
413
  // src/adapters/base/api-client.ts
@@ -811,7 +823,7 @@ var VercelAdapter = class extends BaseAdapter {
811
823
  async getLatestDeployment(project, token) {
812
824
  const apiToken = token || process.env.VERCEL_TOKEN;
813
825
  if (!apiToken) {
814
- throw new Error(ADAPTER_ERRORS.TOKEN_REQUIRED);
826
+ throw new Error(ADAPTER_ERRORS.VERCEL_TOKEN_REQUIRED);
815
827
  }
816
828
  try {
817
829
  const data = await this.api.getDeployments(
@@ -1190,6 +1202,404 @@ var NetlifyAdapter = class extends BaseAdapter {
1190
1202
  }
1191
1203
  };
1192
1204
 
1205
+ // src/adapters/cloudflare-pages/endpoints.ts
1206
+ var CloudflarePagesEndpoints = {
1207
+ // Base URL
1208
+ base: "https://api.cloudflare.com/client/v4",
1209
+ // Projects
1210
+ listProjects: (accountId) => `/accounts/${accountId}/pages/projects`,
1211
+ getProject: (accountId, projectName) => `/accounts/${accountId}/pages/projects/${projectName}`,
1212
+ // Deployments
1213
+ listDeployments: (accountId, projectName) => `/accounts/${accountId}/pages/projects/${projectName}/deployments`,
1214
+ getDeployment: (accountId, projectName, deploymentId) => `/accounts/${accountId}/pages/projects/${projectName}/deployments/${deploymentId}`,
1215
+ createDeployment: (accountId, projectName) => `/accounts/${accountId}/pages/projects/${projectName}/deployments`,
1216
+ deleteDeployment: (accountId, projectName, deploymentId) => `/accounts/${accountId}/pages/projects/${projectName}/deployments/${deploymentId}`,
1217
+ retryDeployment: (accountId, projectName, deploymentId) => `/accounts/${accountId}/pages/projects/${projectName}/deployments/${deploymentId}/retry`,
1218
+ rollbackDeployment: (accountId, projectName, deploymentId) => `/accounts/${accountId}/pages/projects/${projectName}/deployments/${deploymentId}/rollback`,
1219
+ // Logs
1220
+ getDeploymentLogs: (accountId, projectName, deploymentId) => `/accounts/${accountId}/pages/projects/${projectName}/deployments/${deploymentId}/history/logs`,
1221
+ // Documentation URLs
1222
+ docs: {
1223
+ api: "https://developers.cloudflare.com/api/resources/pages/",
1224
+ gettingStarted: "https://developers.cloudflare.com/pages/get-started/",
1225
+ authentication: "https://developers.cloudflare.com/fundamentals/api/get-started/create-token/",
1226
+ deployments: "https://developers.cloudflare.com/pages/configuration/deployments/",
1227
+ buildConfiguration: "https://developers.cloudflare.com/pages/configuration/build-configuration/"
1228
+ }
1229
+ };
1230
+
1231
+ // src/adapters/cloudflare-pages/api.ts
1232
+ var CloudflarePagesAPI = class extends BaseAPIClient {
1233
+ accountId;
1234
+ apiToken;
1235
+ endpoints = {
1236
+ listProjects: {
1237
+ path: "",
1238
+ // Will be set dynamically
1239
+ method: "GET",
1240
+ docsUrl: CloudflarePagesEndpoints.docs.api,
1241
+ description: "List all Cloudflare Pages projects"
1242
+ },
1243
+ getProject: {
1244
+ path: "pages/projects/{projectName}",
1245
+ method: "GET",
1246
+ docsUrl: CloudflarePagesEndpoints.docs.api,
1247
+ description: "Get details for a specific project"
1248
+ },
1249
+ listDeployments: {
1250
+ path: "pages/projects/{projectName}/deployments",
1251
+ method: "GET",
1252
+ docsUrl: CloudflarePagesEndpoints.docs.deployments,
1253
+ description: "List deployments for a project"
1254
+ },
1255
+ getDeployment: {
1256
+ path: "pages/projects/{projectName}/deployments/{deploymentId}",
1257
+ method: "GET",
1258
+ docsUrl: CloudflarePagesEndpoints.docs.deployments,
1259
+ description: "Get details for a specific deployment"
1260
+ },
1261
+ getDeploymentLogs: {
1262
+ path: "pages/projects/{projectName}/deployments/{deploymentId}/history/logs",
1263
+ method: "GET",
1264
+ docsUrl: CloudflarePagesEndpoints.docs.deployments,
1265
+ description: "Get logs for a deployment"
1266
+ },
1267
+ createDeployment: {
1268
+ path: "pages/projects/{projectName}/deployments",
1269
+ method: "POST",
1270
+ docsUrl: CloudflarePagesEndpoints.docs.deployments,
1271
+ description: "Create a new deployment"
1272
+ },
1273
+ retryDeployment: {
1274
+ path: "pages/projects/{projectName}/deployments/{deploymentId}/retry",
1275
+ method: "POST",
1276
+ docsUrl: CloudflarePagesEndpoints.docs.deployments,
1277
+ description: "Retry a failed deployment"
1278
+ }
1279
+ };
1280
+ constructor(accountId, apiToken) {
1281
+ super({
1282
+ baseUrl: CloudflarePagesEndpoints.base,
1283
+ headers: {
1284
+ Authorization: `Bearer ${apiToken}`
1285
+ },
1286
+ timeout: 3e4,
1287
+ retry: 3
1288
+ });
1289
+ this.accountId = accountId;
1290
+ this.apiToken = apiToken;
1291
+ }
1292
+ cleanPath(path) {
1293
+ return path.startsWith("/") ? path.slice(1) : path;
1294
+ }
1295
+ async listProjects(options) {
1296
+ const endpoint = this.endpoints.listProjects;
1297
+ const path = this.cleanPath(
1298
+ CloudflarePagesEndpoints.listProjects(this.accountId)
1299
+ );
1300
+ const response = await this.request(
1301
+ { ...endpoint, path },
1302
+ {
1303
+ searchParams: options?.page || options?.perPage ? {
1304
+ ...options.page && { page: options.page },
1305
+ ...options.perPage && { per_page: options.perPage }
1306
+ } : void 0,
1307
+ token: this.apiToken
1308
+ }
1309
+ );
1310
+ if (!response.success) {
1311
+ throw new Error(
1312
+ `Failed to list projects: ${response.errors?.[0]?.message || "Unknown error"}`
1313
+ );
1314
+ }
1315
+ return response.result;
1316
+ }
1317
+ async getProject(projectName) {
1318
+ const endpoint = this.endpoints.getProject;
1319
+ const path = this.cleanPath(
1320
+ CloudflarePagesEndpoints.getProject(this.accountId, projectName)
1321
+ );
1322
+ const response = await this.request(
1323
+ { ...endpoint, path },
1324
+ { token: this.apiToken }
1325
+ );
1326
+ if (!response.success) {
1327
+ throw new Error(
1328
+ `Failed to get project ${projectName}: ${response.errors?.[0]?.message || "Unknown error"}`
1329
+ );
1330
+ }
1331
+ return response.result;
1332
+ }
1333
+ async listDeployments(projectName, options) {
1334
+ const endpoint = this.endpoints.listDeployments;
1335
+ const path = this.cleanPath(
1336
+ CloudflarePagesEndpoints.listDeployments(this.accountId, projectName)
1337
+ );
1338
+ const searchParams = {};
1339
+ if (options?.page) {
1340
+ searchParams.page = options.page;
1341
+ }
1342
+ if (options?.perPage) {
1343
+ searchParams.per_page = options.perPage;
1344
+ }
1345
+ if (options?.environment) {
1346
+ searchParams.env = options.environment;
1347
+ }
1348
+ const response = await this.request(
1349
+ { ...endpoint, path },
1350
+ {
1351
+ searchParams,
1352
+ token: this.apiToken
1353
+ }
1354
+ );
1355
+ if (!response.success) {
1356
+ throw new Error(
1357
+ `Failed to list deployments for ${projectName}: ${response.errors?.[0]?.message || "Unknown error"}`
1358
+ );
1359
+ }
1360
+ return response.result;
1361
+ }
1362
+ async getDeployment(projectName, deploymentId) {
1363
+ const endpoint = this.endpoints.getDeployment;
1364
+ const path = this.cleanPath(
1365
+ CloudflarePagesEndpoints.getDeployment(
1366
+ this.accountId,
1367
+ projectName,
1368
+ deploymentId
1369
+ )
1370
+ );
1371
+ const response = await this.request(
1372
+ { ...endpoint, path },
1373
+ { token: this.apiToken }
1374
+ );
1375
+ if (!response.success) {
1376
+ throw new Error(
1377
+ `Failed to get deployment ${deploymentId}: ${response.errors?.[0]?.message || "Unknown error"}`
1378
+ );
1379
+ }
1380
+ return response.result;
1381
+ }
1382
+ async getLatestDeployment(projectName, environment = "production") {
1383
+ const deployments = await this.listDeployments(projectName, {
1384
+ environment
1385
+ });
1386
+ return deployments[0] || null;
1387
+ }
1388
+ async getDeploymentLogs(projectName, deploymentId) {
1389
+ const endpoint = this.endpoints.getDeploymentLogs;
1390
+ const path = this.cleanPath(
1391
+ CloudflarePagesEndpoints.getDeploymentLogs(
1392
+ this.accountId,
1393
+ projectName,
1394
+ deploymentId
1395
+ )
1396
+ );
1397
+ const response = await this.request(
1398
+ { ...endpoint, path },
1399
+ { token: this.apiToken }
1400
+ );
1401
+ if (!response.success) {
1402
+ throw new Error(
1403
+ `Failed to get deployment logs: ${response.errors?.[0]?.message || "Unknown error"}`
1404
+ );
1405
+ }
1406
+ return response;
1407
+ }
1408
+ async createDeployment(projectName, branch) {
1409
+ const endpoint = this.endpoints.createDeployment;
1410
+ const path = this.cleanPath(
1411
+ CloudflarePagesEndpoints.createDeployment(this.accountId, projectName)
1412
+ );
1413
+ const body = branch ? { branch } : {};
1414
+ const response = await this.request(
1415
+ { ...endpoint, path },
1416
+ {
1417
+ body,
1418
+ token: this.apiToken
1419
+ }
1420
+ );
1421
+ if (!response.success) {
1422
+ throw new Error(
1423
+ `Failed to create deployment: ${response.errors?.[0]?.message || "Unknown error"}`
1424
+ );
1425
+ }
1426
+ return response.result;
1427
+ }
1428
+ async retryDeployment(projectName, deploymentId) {
1429
+ const endpoint = this.endpoints.retryDeployment;
1430
+ const path = this.cleanPath(
1431
+ CloudflarePagesEndpoints.retryDeployment(
1432
+ this.accountId,
1433
+ projectName,
1434
+ deploymentId
1435
+ )
1436
+ );
1437
+ const response = await this.request(
1438
+ { ...endpoint, path },
1439
+ { token: this.apiToken }
1440
+ );
1441
+ if (!response.success) {
1442
+ throw new Error(
1443
+ `Failed to retry deployment: ${response.errors?.[0]?.message || "Unknown error"}`
1444
+ );
1445
+ }
1446
+ return response.result;
1447
+ }
1448
+ };
1449
+
1450
+ // src/adapters/cloudflare-pages/index.ts
1451
+ var CloudflarePagesAdapter = class extends BaseAdapter {
1452
+ name = "cloudflare-pages";
1453
+ api = null;
1454
+ getAPI(token) {
1455
+ let accountId;
1456
+ let apiToken;
1457
+ if (token.includes(":")) {
1458
+ [accountId, apiToken] = token.split(":", 2);
1459
+ } else {
1460
+ accountId = process.env.CLOUDFLARE_ACCOUNT_ID || "";
1461
+ apiToken = token;
1462
+ }
1463
+ if (!accountId) {
1464
+ throw new Error(
1465
+ "Cloudflare account ID is required. Provide it as 'accountId:apiToken' or set CLOUDFLARE_ACCOUNT_ID environment variable"
1466
+ );
1467
+ }
1468
+ if (!this.api || this.api["accountId"] !== accountId) {
1469
+ this.api = new CloudflarePagesAPI(accountId, apiToken);
1470
+ }
1471
+ return this.api;
1472
+ }
1473
+ async getLatestDeployment(project, token) {
1474
+ const apiToken = token || process.env.CLOUDFLARE_TOKEN || process.env.CLOUDFLARE_API_TOKEN;
1475
+ if (!apiToken) {
1476
+ throw new Error("Cloudflare API token is required");
1477
+ }
1478
+ try {
1479
+ const api = this.getAPI(apiToken);
1480
+ const deployment = await api.getLatestDeployment(project);
1481
+ if (!deployment) {
1482
+ return {
1483
+ status: "unknown",
1484
+ projectName: project,
1485
+ platform: "cloudflare-pages"
1486
+ };
1487
+ }
1488
+ return this.transformDeployment(deployment);
1489
+ } catch (error) {
1490
+ if (error instanceof Error) {
1491
+ throw error;
1492
+ }
1493
+ throw new Error(`Failed to fetch deployment: ${error}`);
1494
+ }
1495
+ }
1496
+ async authenticate(token) {
1497
+ try {
1498
+ const api = this.getAPI(token);
1499
+ await api.listProjects({ perPage: 1 });
1500
+ return true;
1501
+ } catch {
1502
+ return false;
1503
+ }
1504
+ }
1505
+ async getDeploymentById(deploymentId, token) {
1506
+ if (!deploymentId.includes(":")) {
1507
+ throw new Error(
1508
+ "Deployment ID must be in format 'projectName:deploymentId' for Cloudflare Pages"
1509
+ );
1510
+ }
1511
+ const [projectName, actualDeploymentId] = deploymentId.split(":", 2);
1512
+ const api = this.getAPI(token);
1513
+ return api.getDeployment(projectName, actualDeploymentId);
1514
+ }
1515
+ async getRecentDeployments(project, token, _limit = 10) {
1516
+ const api = this.getAPI(token);
1517
+ return api.listDeployments(project);
1518
+ }
1519
+ async getDeploymentLogs(deploymentId, token) {
1520
+ if (!deploymentId.includes(":")) {
1521
+ throw new Error(
1522
+ "Deployment ID must be in format 'projectName:deploymentId' for Cloudflare Pages"
1523
+ );
1524
+ }
1525
+ const [projectName, actualDeploymentId] = deploymentId.split(":", 2);
1526
+ const api = this.getAPI(token);
1527
+ const response = await api.getDeploymentLogs(
1528
+ projectName,
1529
+ actualDeploymentId
1530
+ );
1531
+ if (!response.result?.data || response.result.data.length === 0) {
1532
+ return "No logs available";
1533
+ }
1534
+ return response.result.data.map(
1535
+ (log) => `[${log.timestamp}] ${log.level.toUpperCase()}: ${log.message}`
1536
+ ).join("\n");
1537
+ }
1538
+ async listProjects(token, _limit = 20) {
1539
+ const api = this.getAPI(token);
1540
+ const projects = await api.listProjects();
1541
+ return projects.map((project) => ({
1542
+ id: project.id,
1543
+ name: project.name,
1544
+ url: project.domains?.[0] ? `https://${project.domains[0]}` : `https://${project.subdomain}.pages.dev`
1545
+ }));
1546
+ }
1547
+ transformDeployment(deployment) {
1548
+ const status = this.mapStageStatus(deployment.latest_stage?.status);
1549
+ return {
1550
+ id: deployment.id,
1551
+ status,
1552
+ url: deployment.url,
1553
+ projectName: deployment.project_name,
1554
+ platform: "cloudflare-pages",
1555
+ timestamp: this.formatTimestamp(deployment.created_on),
1556
+ duration: deployment.latest_stage ? this.calculateDuration(
1557
+ deployment.latest_stage.started_on || deployment.created_on,
1558
+ deployment.latest_stage.ended_on || void 0
1559
+ ) : void 0,
1560
+ environment: deployment.environment,
1561
+ commit: deployment.source?.config ? {
1562
+ sha: deployment.source.config.commit_hash,
1563
+ message: deployment.source.config.commit_message,
1564
+ author: void 0
1565
+ // Cloudflare Pages doesn't provide author in API
1566
+ } : void 0
1567
+ };
1568
+ }
1569
+ mapStageStatus(status) {
1570
+ if (!status) {
1571
+ return "unknown";
1572
+ }
1573
+ switch (status) {
1574
+ case "success":
1575
+ return "success";
1576
+ case "failed":
1577
+ case "canceled":
1578
+ return "failed";
1579
+ case "active":
1580
+ return "building";
1581
+ case "skipped":
1582
+ return "unknown";
1583
+ default:
1584
+ return "unknown";
1585
+ }
1586
+ }
1587
+ // Helper method to get deployment status
1588
+ async getDeploymentStatus(project, token) {
1589
+ return this.getLatestDeployment(project, token);
1590
+ }
1591
+ // Helper method to trigger a new deployment
1592
+ async triggerDeployment(project, token, branch) {
1593
+ const api = this.getAPI(token);
1594
+ return api.createDeployment(project, branch);
1595
+ }
1596
+ // Helper method to retry a failed deployment
1597
+ async retryDeployment(projectName, deploymentId, token) {
1598
+ const api = this.getAPI(token);
1599
+ return api.retryDeployment(projectName, deploymentId);
1600
+ }
1601
+ };
1602
+
1193
1603
  // src/core/deployment-intelligence.ts
1194
1604
  var DeploymentIntelligence = class {
1195
1605
  adapter;
@@ -1204,10 +1614,8 @@ var DeploymentIntelligence = class {
1204
1614
  return process.env.VERCEL_TOKEN;
1205
1615
  case "netlify":
1206
1616
  return process.env.NETLIFY_TOKEN;
1207
- case "railway":
1208
- return process.env.RAILWAY_TOKEN;
1209
- case "render":
1210
- return process.env.RENDER_TOKEN;
1617
+ case "cloudflare-pages":
1618
+ return process.env.CLOUDFLARE_TOKEN;
1211
1619
  default:
1212
1620
  return void 0;
1213
1621
  }
@@ -1248,11 +1656,11 @@ var DeploymentIntelligence = class {
1248
1656
  return new VercelAdapter();
1249
1657
  case "netlify":
1250
1658
  return new NetlifyAdapter();
1659
+ case "cloudflare-pages":
1660
+ return new CloudflarePagesAdapter();
1251
1661
  // Ready for future platforms
1252
- // case "railway":
1253
- // return new RailwayAdapter();
1254
- // case "render":
1255
- // return new RenderAdapter();
1662
+ // case "github-pages":
1663
+ // return new GitHubPagesAdapter();
1256
1664
  default:
1257
1665
  throw new Error(`Unsupported platform: ${platform}`);
1258
1666
  }
@@ -2050,9 +2458,12 @@ var MCPHandler = class {
2050
2458
  const formattedStatus = this.formatResponse(status, validated.platform);
2051
2459
  return ResponseFormatter.formatStatus(formattedStatus);
2052
2460
  } else {
2461
+ const tokenEnvKey = `${validated.platform.toUpperCase().replace(/-/g, "_")}_TOKEN`;
2462
+ const generalTokenKey = validated.platform.startsWith("cloudflare-") ? "CLOUDFLARE_TOKEN" : validated.platform.startsWith("github-") ? "GITHUB_TOKEN" : null;
2463
+ const token = validated.token || process.env[tokenEnvKey] || (generalTokenKey ? process.env[generalTokenKey] : null) || "";
2053
2464
  const deployments = await adapter.getRecentDeployments(
2054
2465
  validated.project,
2055
- validated.token || process.env[`${validated.platform.toUpperCase()}_TOKEN`] || "",
2466
+ token,
2056
2467
  validated.limit
2057
2468
  );
2058
2469
  if (deployments.length === 0) {
@@ -2108,6 +2519,27 @@ var MCPHandler = class {
2108
2519
  author: deployment.committer
2109
2520
  } : void 0
2110
2521
  };
2522
+ } else if (platform === PLATFORM.CLOUDFLARE_PAGES) {
2523
+ return {
2524
+ id: deployment.id,
2525
+ status: this.mapCloudflareState(
2526
+ deployment.latest_stage?.status || "unknown"
2527
+ ),
2528
+ url: deployment.url,
2529
+ projectName: deployment.project_name,
2530
+ platform,
2531
+ timestamp: deployment.created_on,
2532
+ duration: deployment.latest_stage?.ended_on && deployment.latest_stage?.started_on ? Math.floor(
2533
+ (new Date(deployment.latest_stage.ended_on).getTime() - new Date(deployment.latest_stage.started_on).getTime()) / 1e3
2534
+ ) : void 0,
2535
+ environment: deployment.environment || "production",
2536
+ commit: deployment.source?.config ? {
2537
+ sha: deployment.source.config.commit_hash,
2538
+ message: deployment.source.config.commit_message,
2539
+ author: void 0
2540
+ // Cloudflare doesn't provide author
2541
+ } : void 0
2542
+ };
2111
2543
  }
2112
2544
  return deployment;
2113
2545
  }
@@ -2138,6 +2570,21 @@ var MCPHandler = class {
2138
2570
  return "building";
2139
2571
  }
2140
2572
  }
2573
+ mapCloudflareState(state) {
2574
+ switch (state) {
2575
+ case CLOUDFLARE_PAGES_STATES.SUCCESS:
2576
+ return "success";
2577
+ case CLOUDFLARE_PAGES_STATES.FAILED:
2578
+ case CLOUDFLARE_PAGES_STATES.CANCELED:
2579
+ return "failed";
2580
+ case CLOUDFLARE_PAGES_STATES.ACTIVE:
2581
+ return "building";
2582
+ case CLOUDFLARE_PAGES_STATES.SKIPPED:
2583
+ return "unknown";
2584
+ default:
2585
+ return "unknown";
2586
+ }
2587
+ }
2141
2588
  formatResponse(status, platform) {
2142
2589
  return {
2143
2590
  ...status,
@@ -2217,11 +2664,13 @@ ${messages.join("\n\n")}
2217
2664
  if (!adapter) {
2218
2665
  throw new Error(`Unsupported platform: ${validated.platform}`);
2219
2666
  }
2220
- const tokenEnvKey = `${validated.platform.toUpperCase()}_TOKEN`;
2221
- const token = validated.token || process.env[tokenEnvKey];
2667
+ const tokenEnvKey = `${validated.platform.toUpperCase().replace(/-/g, "_")}_TOKEN`;
2668
+ const generalTokenKey = validated.platform.startsWith("cloudflare-") ? "CLOUDFLARE_TOKEN" : validated.platform.startsWith("github-") ? "GITHUB_TOKEN" : null;
2669
+ const token = validated.token || process.env[tokenEnvKey] || (generalTokenKey ? process.env[generalTokenKey] : null);
2222
2670
  if (!token) {
2671
+ const tokenOptions = generalTokenKey ? `${tokenEnvKey} or ${generalTokenKey} environment variable` : `${tokenEnvKey} environment variable`;
2223
2672
  throw new Error(
2224
- `Authentication required. Please provide a token or set ${tokenEnvKey} environment variable.`
2673
+ `Authentication required. Please provide a token or set ${tokenOptions}.`
2225
2674
  );
2226
2675
  }
2227
2676
  try {
@@ -2280,5 +2729,6 @@ export {
2280
2729
  tools,
2281
2730
  VercelAdapter,
2282
2731
  NetlifyAdapter,
2732
+ CloudflarePagesAdapter,
2283
2733
  MCPHandler
2284
2734
  };
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ CloudflarePagesAdapter,
3
4
  MCPHandler,
4
5
  NetlifyAdapter,
5
6
  VercelAdapter,
6
7
  tools
7
- } from "./chunk-YU5SU5ZI.js";
8
+ } from "./chunk-BFOEFRRT.js";
8
9
 
9
10
  // src/index.ts
10
11
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -16,7 +17,8 @@ import {
16
17
  var handler = new MCPHandler(
17
18
  /* @__PURE__ */ new Map([
18
19
  ["vercel", new VercelAdapter()],
19
- ["netlify", new NetlifyAdapter()]
20
+ ["netlify", new NetlifyAdapter()],
21
+ ["cloudflare-pages", new CloudflarePagesAdapter()]
20
22
  ])
21
23
  );
22
24
  var server = new Server(
package/dist/worker.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  MCPHandler,
3
3
  VercelAdapter
4
- } from "./chunk-YU5SU5ZI.js";
4
+ } from "./chunk-BFOEFRRT.js";
5
5
 
6
6
  // src/utils/github.ts
7
7
  async function validateRepository(user, repo) {
@@ -32,7 +32,7 @@ async function validateRepository(user, repo) {
32
32
  }
33
33
  function validateParams(user, repo, platform) {
34
34
  const validName = /^[a-zA-Z0-9._-]+$/;
35
- const validPlatforms = ["vercel", "netlify", "railway"];
35
+ const validPlatforms = ["vercel", "netlify", "cloudflare-pages"];
36
36
  return validName.test(user) && validName.test(repo) && validPlatforms.includes(platform);
37
37
  }
38
38
 
@@ -53,9 +53,9 @@ var PLATFORM_CONFIG = {
53
53
  label: "Netlify",
54
54
  logo: "netlify"
55
55
  },
56
- railway: {
57
- label: "Railway",
58
- logo: "railway"
56
+ "cloudflare-pages": {
57
+ label: "Cloudflare Pages",
58
+ logo: "cloudflare"
59
59
  }
60
60
  };
61
61
  function validateParams2(user, repo, platform) {
@@ -651,23 +651,23 @@ var landingPageHTML = `<!DOCTYPE html>
651
651
  </p>
652
652
  </div>
653
653
 
654
- <div class="platform">
654
+ <div class="platform active">
655
655
  <div class="platform-header">
656
- <span class="platform-name">Railway</span>
657
- <span class="platform-status soon">Soon</span>
656
+ <span class="platform-name">Cloudflare Pages</span>
657
+ <span class="platform-status">Ready</span>
658
658
  </div>
659
659
  <p class="platform-desc">
660
- Railway integration with service monitoring coming next.
660
+ Fast edge deployments with free bandwidth and global CDN.
661
661
  </p>
662
662
  </div>
663
663
 
664
664
  <div class="platform">
665
665
  <div class="platform-header">
666
- <span class="platform-name">Render</span>
666
+ <span class="platform-name">GitHub Pages</span>
667
667
  <span class="platform-status soon">Soon</span>
668
668
  </div>
669
669
  <p class="platform-desc">
670
- Render support with service and database monitoring planned.
670
+ Direct GitHub integration with Actions-based deployment tracking.
671
671
  </p>
672
672
  </div>
673
673
  </div>
@@ -680,7 +680,8 @@ var landingPageHTML = `<!DOCTYPE html>
680
680
  <pre style="margin: 0;">{
681
681
  "env": {
682
682
  "VERCEL_TOKEN": "your-vercel-token",
683
- "NETLIFY_TOKEN": "your-netlify-token"
683
+ "NETLIFY_TOKEN": "your-netlify-token",
684
+ "CLOUDFLARE_TOKEN": "accountId:globalApiKey"
684
685
  }
685
686
  }</pre>
686
687
  </div>
@@ -767,7 +768,8 @@ var landingPageHTML = `<!DOCTYPE html>
767
768
  "args": ["-y", "deploy-mcp"],
768
769
  "env": {
769
770
  "VERCEL_TOKEN": "your-vercel-token",
770
- "NETLIFY_TOKEN": "your-netlify-token"
771
+ "NETLIFY_TOKEN": "your-netlify-token",
772
+ "CLOUDFLARE_TOKEN": "accountId:globalApiKey"
771
773
  }
772
774
  }
773
775
  }
@@ -822,7 +824,7 @@ var landingPageHTML = `<!DOCTYPE html>
822
824
  <div class="badge-examples">
823
825
  <img src="https://img.shields.io/badge/vercel-success-22c55e" alt="Vercel Status">
824
826
  <img src="https://img.shields.io/badge/netlify-building-FCD34D" alt="Netlify Status">
825
- <img src="https://img.shields.io/badge/railway-coming%20soon-gray" alt="Railway Status">
827
+ <img src="https://img.shields.io/badge/cloudflare%20pages-ready-22c55e" alt="Cloudflare Pages Status">
826
828
  </div>
827
829
 
828
830
  <div class="badge-code">
@@ -875,7 +877,8 @@ var landingPageHTML = `<!DOCTYPE html>
875
877
  args: ['-y', 'deploy-mcp'],
876
878
  env: {
877
879
  VERCEL_TOKEN: 'your-vercel-token',
878
- NETLIFY_TOKEN: 'your-netlify-token'
880
+ NETLIFY_TOKEN: 'your-netlify-token',
881
+ CLOUDFLARE_TOKEN: 'accountId:globalApiKey'
879
882
  }
880
883
  }
881
884
  }
@@ -887,7 +890,8 @@ var landingPageHTML = `<!DOCTYPE html>
887
890
  const config = JSON.stringify({
888
891
  env: {
889
892
  VERCEL_TOKEN: 'your-vercel-token',
890
- NETLIFY_TOKEN: 'your-netlify-token'
893
+ NETLIFY_TOKEN: 'your-netlify-token',
894
+ CLOUDFLARE_TOKEN: 'accountId:globalApiKey'
891
895
  }
892
896
  }, null, 2);
893
897
  copyToClipboard(config, event);
@@ -932,6 +936,9 @@ async function validateWebhookSignature(request, platform, env) {
932
936
  return false;
933
937
  }
934
938
  }
939
+ if (platform === "cloudflare-pages") {
940
+ return true;
941
+ }
935
942
  return true;
936
943
  }
937
944
  function mapDeploymentStatus(platform, payload) {
@@ -964,8 +971,22 @@ function mapDeploymentStatus(platform, payload) {
964
971
  return "unknown";
965
972
  }
966
973
  }
967
- case "railway":
968
- return "unknown";
974
+ case "cloudflare-pages": {
975
+ const cloudflarePagesPayload = payload;
976
+ switch (cloudflarePagesPayload.status) {
977
+ case "success":
978
+ return "success";
979
+ case "failed":
980
+ case "canceled":
981
+ return "failed";
982
+ case "active":
983
+ return "building";
984
+ case "skipped":
985
+ return "unknown";
986
+ default:
987
+ return "unknown";
988
+ }
989
+ }
969
990
  default:
970
991
  return "unknown";
971
992
  }
package/package.json CHANGED
@@ -1,17 +1,19 @@
1
1
  {
2
2
  "name": "deploy-mcp",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Universal deployment tracker for AI assistants",
5
5
  "keywords": [
6
6
  "mcp",
7
7
  "deployment",
8
8
  "vercel",
9
9
  "netlify",
10
+ "cloudflare-pages",
10
11
  "ai",
11
12
  "claude",
12
13
  "deployment-tracking",
13
14
  "netlify-deployment",
14
15
  "vercel-deployment",
16
+ "cloudflare-deployment",
15
17
  "deployment-status",
16
18
  "deployment-monitor",
17
19
  "ci-cd"