@tupaas/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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import os from "os";
8
+ import { execSync } from "child_process";
9
+ const API_URL = process.env.TUPAAS_API_URL ?? "https://tupaas.dev/api";
10
+ const API_KEY = process.env.TUPAAS_API_KEY ?? "";
11
+ const server = new Server({ name: "tupaas-mcp", version: "1.0.0" }, { capabilities: { tools: {} } });
12
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
13
+ tools: [
14
+ {
15
+ name: "deploy",
16
+ description: "Deploy a project folder to TuPaaS. Compresses the folder, uploads it, and returns the live URL.",
17
+ inputSchema: {
18
+ type: "object",
19
+ properties: {
20
+ project_path: {
21
+ type: "string",
22
+ description: "Absolute path to the project folder to deploy",
23
+ },
24
+ app_name: {
25
+ type: "string",
26
+ description: "Name for the app (optional, inferred from directory name)",
27
+ },
28
+ },
29
+ required: ["project_path"],
30
+ },
31
+ },
32
+ {
33
+ name: "deploy_status",
34
+ description: "Check the status of a TuPaaS deployment",
35
+ inputSchema: {
36
+ type: "object",
37
+ properties: {
38
+ deploy_id: {
39
+ type: "string",
40
+ description: "The deploy ID to check",
41
+ },
42
+ },
43
+ required: ["deploy_id"],
44
+ },
45
+ },
46
+ ],
47
+ }));
48
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
49
+ if (!API_KEY) {
50
+ return {
51
+ content: [
52
+ {
53
+ type: "text",
54
+ text: "Error: TUPAAS_API_KEY environment variable not set. Get your key from https://tupaas.dev/dashboard/api-keys",
55
+ },
56
+ ],
57
+ };
58
+ }
59
+ switch (request.params.name) {
60
+ case "deploy": {
61
+ const args = request.params.arguments;
62
+ const projectPath = args.project_path;
63
+ const appName = args.app_name ?? path.basename(projectPath);
64
+ // Validate path exists
65
+ if (!fs.existsSync(projectPath)) {
66
+ return {
67
+ content: [
68
+ {
69
+ type: "text",
70
+ text: `Error: Path "${projectPath}" does not exist`,
71
+ },
72
+ ],
73
+ };
74
+ }
75
+ // Compress as ZIP (server expects ZIP format)
76
+ const tarPath = path.join(os.tmpdir(), `${appName}.zip`);
77
+ try {
78
+ execSync(`cd "${projectPath}" && zip -r "${tarPath}" . -x 'node_modules/*' '.git/*' '.next/*' 'dist/*'`);
79
+ }
80
+ catch {
81
+ return {
82
+ content: [
83
+ {
84
+ type: "text",
85
+ text: "Error: Failed to compress project folder",
86
+ },
87
+ ],
88
+ };
89
+ }
90
+ const stats = fs.statSync(tarPath);
91
+ if (stats.size > 50 * 1024 * 1024) {
92
+ fs.unlinkSync(tarPath);
93
+ return {
94
+ content: [
95
+ {
96
+ type: "text",
97
+ text: "Error: Compressed file exceeds 50MB limit",
98
+ },
99
+ ],
100
+ };
101
+ }
102
+ // Upload
103
+ const fileBuffer = fs.readFileSync(tarPath);
104
+ const formData = new FormData();
105
+ formData.append("file", new Blob([fileBuffer]), `${appName}.zip`);
106
+ formData.append("name", appName);
107
+ const res = await fetch(`${API_URL}/deploy`, {
108
+ method: "POST",
109
+ headers: { "x-api-key": API_KEY },
110
+ body: formData,
111
+ });
112
+ fs.unlinkSync(tarPath);
113
+ if (!res.ok) {
114
+ const err = (await res.json());
115
+ return {
116
+ content: [
117
+ {
118
+ type: "text",
119
+ text: `Deploy failed: ${err.error}`,
120
+ },
121
+ ],
122
+ };
123
+ }
124
+ const result = (await res.json());
125
+ // Poll until done
126
+ let finalStatus = "QUEUED";
127
+ let appUrl = result.appUrl;
128
+ let lastLogs = "";
129
+ const maxPolls = 120;
130
+ for (let i = 0; i < maxPolls; i++) {
131
+ await new Promise((r) => setTimeout(r, 5000));
132
+ const statusRes = await fetch(`${API_URL}/deploys/${result.deployId}/status`, { headers: { "x-api-key": API_KEY } });
133
+ if (!statusRes.ok)
134
+ break;
135
+ const status = (await statusRes.json());
136
+ finalStatus = status.status;
137
+ if (status.appUrl)
138
+ appUrl = status.appUrl;
139
+ if (status.logs)
140
+ lastLogs = status.logs;
141
+ if (status.done)
142
+ break;
143
+ }
144
+ if (finalStatus === "SUCCESS") {
145
+ return {
146
+ content: [
147
+ {
148
+ type: "text",
149
+ text: `Deploy successful!\n\nProject: ${result.projectSlug}\nURL: ${appUrl}\nDeploy ID: ${result.deployId}`,
150
+ },
151
+ ],
152
+ };
153
+ }
154
+ else {
155
+ return {
156
+ content: [
157
+ {
158
+ type: "text",
159
+ text: `Deploy failed with status: ${finalStatus}\n\nDeploy ID: ${result.deployId}\nProject: ${result.projectSlug}\n\n--- Build Logs ---\n${lastLogs || "No logs available"}\n\n--- End Logs ---\nDashboard: http://localhost:3000/dashboard/projects/${result.projectSlug}`,
160
+ },
161
+ ],
162
+ };
163
+ }
164
+ }
165
+ case "deploy_status": {
166
+ const args = request.params.arguments;
167
+ const res = await fetch(`${API_URL}/deploys/${args.deploy_id}/status`, { headers: { "x-api-key": API_KEY } });
168
+ if (!res.ok) {
169
+ return {
170
+ content: [
171
+ {
172
+ type: "text",
173
+ text: `Error: Could not fetch deploy status (${res.status})`,
174
+ },
175
+ ],
176
+ };
177
+ }
178
+ const data = (await res.json());
179
+ return {
180
+ content: [
181
+ {
182
+ type: "text",
183
+ text: JSON.stringify(data, null, 2),
184
+ },
185
+ ],
186
+ };
187
+ }
188
+ default:
189
+ return {
190
+ content: [
191
+ {
192
+ type: "text",
193
+ text: `Unknown tool: ${request.params.name}`,
194
+ },
195
+ ],
196
+ };
197
+ }
198
+ });
199
+ async function main() {
200
+ const transport = new StdioServerTransport();
201
+ await server.connect(transport);
202
+ }
203
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@tupaas/mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for deploying to TuPaaS from Claude Code",
5
+ "type": "module",
6
+ "bin": {
7
+ "tupaas-mcp": "./dist/index.js"
8
+ },
9
+ "files": ["dist/"],
10
+ "engines": {
11
+ "node": ">=18.0.0"
12
+ },
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "dev": "tsx src/index.ts"
16
+ },
17
+ "dependencies": {
18
+ "@modelcontextprotocol/sdk": "^1.0.0"
19
+ },
20
+ "devDependencies": {
21
+ "tsx": "^4.0.0",
22
+ "typescript": "^5.8.0"
23
+ }
24
+ }