deploy-mcp 0.1.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/CHANGELOG.md +45 -0
- package/LICENSE +55 -0
- package/README.md +499 -0
- package/SECURITY.md +61 -0
- package/dist/chunk-QRZL43CY.js +297 -0
- package/dist/index.js +95 -0
- package/dist/worker.js +1212 -0
- package/package.json +80 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
// src/core/tools.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var checkDeploymentStatusSchema = z.object({
|
|
4
|
+
platform: z.enum(["vercel"]).describe("The deployment platform (more platforms coming soon)"),
|
|
5
|
+
project: z.string().describe("The project name or ID"),
|
|
6
|
+
token: z.string().optional().describe("API token for authentication (optional if set in environment)")
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/core/mcp-handler.ts
|
|
10
|
+
var MCPHandler = class {
|
|
11
|
+
constructor(adapters) {
|
|
12
|
+
this.adapters = adapters;
|
|
13
|
+
}
|
|
14
|
+
async handleToolCall(tool, args) {
|
|
15
|
+
switch (tool) {
|
|
16
|
+
case "check_deployment_status":
|
|
17
|
+
return this.checkDeploymentStatus(args);
|
|
18
|
+
default:
|
|
19
|
+
throw new Error(`Unknown tool: ${tool}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async handleRequest(request) {
|
|
23
|
+
if (request.method === "tools/call") {
|
|
24
|
+
const { name, arguments: args } = request.params;
|
|
25
|
+
const result = await this.handleToolCall(name, args);
|
|
26
|
+
return {
|
|
27
|
+
content: [
|
|
28
|
+
{
|
|
29
|
+
type: "text",
|
|
30
|
+
text: JSON.stringify(result, null, 2)
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (request.method === "tools/list") {
|
|
36
|
+
return {
|
|
37
|
+
tools: [
|
|
38
|
+
{
|
|
39
|
+
name: "check_deployment_status",
|
|
40
|
+
description: "Check the latest deployment status for a project on a platform",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
platform: {
|
|
45
|
+
type: "string",
|
|
46
|
+
enum: ["vercel"],
|
|
47
|
+
description: "The deployment platform"
|
|
48
|
+
},
|
|
49
|
+
project: {
|
|
50
|
+
type: "string",
|
|
51
|
+
description: "The project name or ID"
|
|
52
|
+
},
|
|
53
|
+
token: {
|
|
54
|
+
type: "string",
|
|
55
|
+
description: "API token for authentication (optional if set in environment)"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
required: ["platform", "project"]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
throw new Error(`Unknown method: ${request.method}`);
|
|
65
|
+
}
|
|
66
|
+
async checkDeploymentStatus(args) {
|
|
67
|
+
const validated = checkDeploymentStatusSchema.parse(args);
|
|
68
|
+
const adapter = this.adapters.get(validated.platform);
|
|
69
|
+
if (!adapter) {
|
|
70
|
+
throw new Error(`Unsupported platform: ${validated.platform}`);
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const status = await adapter.getLatestDeployment(
|
|
74
|
+
validated.project,
|
|
75
|
+
validated.token
|
|
76
|
+
);
|
|
77
|
+
return this.formatResponse(status, validated.platform);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error(`Error checking deployment status: ${error}`);
|
|
80
|
+
return {
|
|
81
|
+
status: "error",
|
|
82
|
+
platform: validated.platform,
|
|
83
|
+
projectName: validated.project,
|
|
84
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
formatResponse(status, platform) {
|
|
89
|
+
return {
|
|
90
|
+
...status,
|
|
91
|
+
platform,
|
|
92
|
+
timestamp: status.timestamp || (/* @__PURE__ */ new Date()).toISOString()
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/adapters/base/adapter.ts
|
|
98
|
+
var BaseAdapter = class {
|
|
99
|
+
formatTimestamp(date) {
|
|
100
|
+
return new Date(date).toISOString();
|
|
101
|
+
}
|
|
102
|
+
calculateDuration(start, end) {
|
|
103
|
+
const startTime = new Date(start).getTime();
|
|
104
|
+
const endTime = end ? new Date(end).getTime() : Date.now();
|
|
105
|
+
return Math.floor((endTime - startTime) / 1e3);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// src/adapters/base/types.ts
|
|
110
|
+
var AdapterException = class extends Error {
|
|
111
|
+
constructor(type, message, originalError) {
|
|
112
|
+
super(message);
|
|
113
|
+
this.type = type;
|
|
114
|
+
this.originalError = originalError;
|
|
115
|
+
this.name = "AdapterException";
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// src/adapters/vercel/api.ts
|
|
120
|
+
var VercelAPI = class {
|
|
121
|
+
config;
|
|
122
|
+
constructor(config) {
|
|
123
|
+
this.config = config;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get deployments for a project
|
|
127
|
+
*/
|
|
128
|
+
async getDeployments(projectId, token, limit = 1) {
|
|
129
|
+
const url = `${this.config.baseUrl}/v6/deployments?projectId=${projectId}&limit=${limit}`;
|
|
130
|
+
try {
|
|
131
|
+
const response = await this.makeRequest(url, token);
|
|
132
|
+
return await response.json();
|
|
133
|
+
} catch (error) {
|
|
134
|
+
throw this.handleApiError(
|
|
135
|
+
error,
|
|
136
|
+
`Failed to fetch deployments for project ${projectId}`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get user information to validate token
|
|
142
|
+
*/
|
|
143
|
+
async getUser(token) {
|
|
144
|
+
const url = `${this.config.baseUrl}/v2/user`;
|
|
145
|
+
try {
|
|
146
|
+
const response = await this.makeRequest(url, token);
|
|
147
|
+
return await response.json();
|
|
148
|
+
} catch (error) {
|
|
149
|
+
throw this.handleApiError(error, "Failed to validate Vercel token");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Make authenticated request to Vercel API
|
|
154
|
+
*/
|
|
155
|
+
async makeRequest(url, token) {
|
|
156
|
+
const controller = new AbortController();
|
|
157
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
158
|
+
try {
|
|
159
|
+
const response = await fetch(url, {
|
|
160
|
+
signal: controller.signal,
|
|
161
|
+
headers: {
|
|
162
|
+
Authorization: `Bearer ${token}`,
|
|
163
|
+
"Content-Type": "application/json",
|
|
164
|
+
"User-Agent": "deploy-mcp/1.0.0"
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
clearTimeout(timeoutId);
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
170
|
+
}
|
|
171
|
+
return response;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
clearTimeout(timeoutId);
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Handle and categorize API errors
|
|
179
|
+
*/
|
|
180
|
+
handleApiError(error, context) {
|
|
181
|
+
if (error instanceof Error) {
|
|
182
|
+
if (error.message.includes("401")) {
|
|
183
|
+
return new AdapterException(
|
|
184
|
+
"UNAUTHORIZED",
|
|
185
|
+
"Invalid Vercel token",
|
|
186
|
+
error
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
if (error.message.includes("404")) {
|
|
190
|
+
return new AdapterException("NOT_FOUND", "Project not found", error);
|
|
191
|
+
}
|
|
192
|
+
if (error.message.includes("429")) {
|
|
193
|
+
return new AdapterException(
|
|
194
|
+
"RATE_LIMITED",
|
|
195
|
+
"Rate limit exceeded",
|
|
196
|
+
error
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (error.name === "AbortError") {
|
|
200
|
+
return new AdapterException("NETWORK_ERROR", "Request timeout", error);
|
|
201
|
+
}
|
|
202
|
+
return new AdapterException("NETWORK_ERROR", context, error);
|
|
203
|
+
}
|
|
204
|
+
return new AdapterException("UNKNOWN_ERROR", context);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// src/adapters/vercel/index.ts
|
|
209
|
+
var VercelAdapter = class extends BaseAdapter {
|
|
210
|
+
name = "vercel";
|
|
211
|
+
api;
|
|
212
|
+
constructor(config) {
|
|
213
|
+
super();
|
|
214
|
+
const defaultConfig = {
|
|
215
|
+
baseUrl: "https://api.vercel.com",
|
|
216
|
+
timeout: 1e4,
|
|
217
|
+
retryAttempts: 3
|
|
218
|
+
};
|
|
219
|
+
this.api = new VercelAPI({ ...defaultConfig, ...config });
|
|
220
|
+
}
|
|
221
|
+
async getLatestDeployment(project, token) {
|
|
222
|
+
const apiToken = token || process.env.VERCEL_TOKEN;
|
|
223
|
+
if (!apiToken) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
"Vercel token required. Set VERCEL_TOKEN environment variable or pass token parameter."
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
const data = await this.api.getDeployments(project, apiToken, 1);
|
|
230
|
+
if (!data.deployments || data.deployments.length === 0) {
|
|
231
|
+
return {
|
|
232
|
+
status: "unknown",
|
|
233
|
+
projectName: project,
|
|
234
|
+
platform: "vercel"
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
return this.transformDeployment(data.deployments[0]);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
if (error instanceof Error) {
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
throw new Error("Failed to fetch deployment status from Vercel");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async authenticate(token) {
|
|
246
|
+
try {
|
|
247
|
+
await this.api.getUser(token);
|
|
248
|
+
return true;
|
|
249
|
+
} catch {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Transform Vercel deployment to standard format
|
|
255
|
+
*/
|
|
256
|
+
transformDeployment(deployment) {
|
|
257
|
+
const status = this.mapState(deployment.state);
|
|
258
|
+
return {
|
|
259
|
+
id: deployment.uid,
|
|
260
|
+
status,
|
|
261
|
+
url: deployment.url ? `https://${deployment.url}` : void 0,
|
|
262
|
+
projectName: deployment.name,
|
|
263
|
+
platform: "vercel",
|
|
264
|
+
timestamp: this.formatTimestamp(deployment.createdAt),
|
|
265
|
+
duration: deployment.ready ? this.calculateDuration(deployment.createdAt, deployment.ready) : void 0,
|
|
266
|
+
environment: deployment.target || "production",
|
|
267
|
+
commit: deployment.meta ? {
|
|
268
|
+
sha: deployment.meta.githubCommitSha,
|
|
269
|
+
message: deployment.meta.githubCommitMessage,
|
|
270
|
+
author: deployment.meta.githubCommitAuthorName
|
|
271
|
+
} : void 0
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Map Vercel states to standard deployment status
|
|
276
|
+
*/
|
|
277
|
+
mapState(state) {
|
|
278
|
+
switch (state) {
|
|
279
|
+
case "READY":
|
|
280
|
+
return "success";
|
|
281
|
+
case "ERROR":
|
|
282
|
+
case "CANCELED":
|
|
283
|
+
return "failed";
|
|
284
|
+
case "BUILDING":
|
|
285
|
+
case "INITIALIZING":
|
|
286
|
+
case "QUEUED":
|
|
287
|
+
return "building";
|
|
288
|
+
default:
|
|
289
|
+
return "unknown";
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
export {
|
|
295
|
+
MCPHandler,
|
|
296
|
+
VercelAdapter
|
|
297
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
MCPHandler,
|
|
4
|
+
VercelAdapter
|
|
5
|
+
} from "./chunk-QRZL43CY.js";
|
|
6
|
+
|
|
7
|
+
// src/index.ts
|
|
8
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
9
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
+
import {
|
|
11
|
+
CallToolRequestSchema,
|
|
12
|
+
ListToolsRequestSchema
|
|
13
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
14
|
+
var handler = new MCPHandler(
|
|
15
|
+
/* @__PURE__ */ new Map([
|
|
16
|
+
["vercel", new VercelAdapter()]
|
|
17
|
+
// ['netlify', new NetlifyAdapter()] // To be implemented
|
|
18
|
+
])
|
|
19
|
+
);
|
|
20
|
+
var server = new Server(
|
|
21
|
+
{
|
|
22
|
+
name: "deploy-mcp",
|
|
23
|
+
version: "0.1.0",
|
|
24
|
+
description: "Universal deployment tracker for AI assistants"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
capabilities: {
|
|
28
|
+
tools: {}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
33
|
+
return {
|
|
34
|
+
tools: [
|
|
35
|
+
{
|
|
36
|
+
name: "check_deployment_status",
|
|
37
|
+
description: "Check the latest deployment status for a project on a platform",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
platform: {
|
|
42
|
+
type: "string",
|
|
43
|
+
enum: ["vercel"],
|
|
44
|
+
description: "The deployment platform"
|
|
45
|
+
},
|
|
46
|
+
project: {
|
|
47
|
+
type: "string",
|
|
48
|
+
description: "The project name or ID"
|
|
49
|
+
},
|
|
50
|
+
token: {
|
|
51
|
+
type: "string",
|
|
52
|
+
description: "API token for authentication (optional if set in environment)"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
required: ["platform", "project"]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
62
|
+
try {
|
|
63
|
+
const result = await handler.handleToolCall(
|
|
64
|
+
request.params.name,
|
|
65
|
+
request.params.arguments
|
|
66
|
+
);
|
|
67
|
+
return {
|
|
68
|
+
content: [
|
|
69
|
+
{
|
|
70
|
+
type: "text",
|
|
71
|
+
text: JSON.stringify(result, null, 2)
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
};
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return {
|
|
77
|
+
content: [
|
|
78
|
+
{
|
|
79
|
+
type: "text",
|
|
80
|
+
text: `Error: ${error instanceof Error ? error.message : "Unknown error occurred"}`
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
isError: true
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
async function main() {
|
|
88
|
+
const transport = new StdioServerTransport();
|
|
89
|
+
await server.connect(transport);
|
|
90
|
+
console.error("Deploy MCP server started");
|
|
91
|
+
}
|
|
92
|
+
main().catch((error) => {
|
|
93
|
+
console.error("Fatal error:", error);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
});
|