@sota-io/mcp 1.4.1 → 1.5.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/dist/lib/archive-builder.d.ts +59 -0
- package/dist/lib/archive-builder.d.ts.map +1 -0
- package/dist/lib/archive-builder.js +221 -0
- package/dist/lib/archive-builder.js.map +1 -0
- package/dist/tools/deploy.d.ts.map +1 -1
- package/dist/tools/deploy.js +89 -19
- package/dist/tools/deploy.js.map +1 -1
- package/dist/tools/projects.d.ts.map +1 -1
- package/dist/tools/projects.js +13 -4
- package/dist/tools/projects.js.map +1 -1
- package/package.json +11 -10
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Archive Builder — turn AI-supplied source into a tar.gz ready for upload.
|
|
3
|
+
*
|
|
4
|
+
* Three input modes for the `deploy` MCP tool:
|
|
5
|
+
* 1. directory: string — existing CLI/Claude-Code flow
|
|
6
|
+
* 2. files: Record<string,string> — inline source from AI agent
|
|
7
|
+
* 3. git_url: string + git_branch?: string — clone a public repo
|
|
8
|
+
*
|
|
9
|
+
* All three produce a tar.gz Buffer that the SDK uploads to
|
|
10
|
+
* `POST /v1/projects/:id/deploy` via multipart/form-data. The backend
|
|
11
|
+
* accepts the multipart upload identically regardless of source.
|
|
12
|
+
*
|
|
13
|
+
* Limits (deliberately generous — we want to lean permissive at launch):
|
|
14
|
+
* files: MAX_INLINE_FILES entries, MAX_INLINE_TOTAL_BYTES total
|
|
15
|
+
* git_url: MAX_CLONE_BYTES on disk after `git clone --depth=1`
|
|
16
|
+
*
|
|
17
|
+
* Security:
|
|
18
|
+
* - Inline file paths are normalised + rejected if they contain ".." or
|
|
19
|
+
* start with "/" or contain NUL.
|
|
20
|
+
* - Symlinks are not honoured for inline files (we only write regular files).
|
|
21
|
+
* - Git clone uses `--depth=1` and `--single-branch` to bound bandwidth.
|
|
22
|
+
* - Both modes write to a fresh /tmp dir which is removed after tar.gz is read.
|
|
23
|
+
*/
|
|
24
|
+
/** Upper bound on inline file count (per single `deploy` call). */
|
|
25
|
+
export declare const MAX_INLINE_FILES = 200;
|
|
26
|
+
/** Upper bound on combined inline content length, in bytes. */
|
|
27
|
+
export declare const MAX_INLINE_TOTAL_BYTES: number;
|
|
28
|
+
/** Upper bound on a cloned git repo size (du -sb on the workdir), in bytes. */
|
|
29
|
+
export declare const MAX_CLONE_BYTES: number;
|
|
30
|
+
export declare class ArchiveBuilderError extends Error {
|
|
31
|
+
readonly code: string;
|
|
32
|
+
constructor(code: string, message: string);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Pack a record of file paths → text content into a tar.gz Buffer.
|
|
36
|
+
*
|
|
37
|
+
* Each value is treated as UTF-8 text. Binary assets are not supported in
|
|
38
|
+
* this mode — use `archiveFromGitUrl` for that. The caller is responsible
|
|
39
|
+
* for including framework manifests (package.json, requirements.txt, or a
|
|
40
|
+
* Dockerfile) so the backend's auto-detection picks the right builder.
|
|
41
|
+
*/
|
|
42
|
+
export declare function archiveFromFiles(files: Record<string, string>): Buffer;
|
|
43
|
+
/**
|
|
44
|
+
* `git clone --depth=1` the given URL (optionally on a specified branch),
|
|
45
|
+
* then pack the resulting workdir into a tar.gz Buffer. Only public repos
|
|
46
|
+
* are supported in v1 — there is no credential plumbing.
|
|
47
|
+
*
|
|
48
|
+
* Rejects URLs that don't begin with https://, http://, git://, ssh://, or
|
|
49
|
+
* the scp-like git@host:path form, to keep the surface predictable.
|
|
50
|
+
*/
|
|
51
|
+
export declare function archiveFromGitUrl(gitUrl: string, gitBranch?: string): Buffer;
|
|
52
|
+
/**
|
|
53
|
+
* Pack an existing local directory into a tar.gz Buffer using the same
|
|
54
|
+
* ignore rules as the directory-mode `deploy` (excludes .git, node_modules,
|
|
55
|
+
* .env, .DS_Store). Exported so the deploy tool can re-use a single
|
|
56
|
+
* code path for archive construction.
|
|
57
|
+
*/
|
|
58
|
+
export declare function archiveFromDirectory(dir: string): Buffer;
|
|
59
|
+
//# sourceMappingURL=archive-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-builder.d.ts","sourceRoot":"","sources":["../../src/lib/archive-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAcH,mEAAmE;AACnE,eAAO,MAAM,gBAAgB,MAAM,CAAC;AACpC,+DAA+D;AAC/D,eAAO,MAAM,sBAAsB,QAAmB,CAAC;AACvD,+EAA+E;AAC/E,eAAO,MAAM,eAAe,QAAoB,CAAC;AAIjD,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBACV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAsCtE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAsD5E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQxD"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Archive Builder — turn AI-supplied source into a tar.gz ready for upload.
|
|
3
|
+
*
|
|
4
|
+
* Three input modes for the `deploy` MCP tool:
|
|
5
|
+
* 1. directory: string — existing CLI/Claude-Code flow
|
|
6
|
+
* 2. files: Record<string,string> — inline source from AI agent
|
|
7
|
+
* 3. git_url: string + git_branch?: string — clone a public repo
|
|
8
|
+
*
|
|
9
|
+
* All three produce a tar.gz Buffer that the SDK uploads to
|
|
10
|
+
* `POST /v1/projects/:id/deploy` via multipart/form-data. The backend
|
|
11
|
+
* accepts the multipart upload identically regardless of source.
|
|
12
|
+
*
|
|
13
|
+
* Limits (deliberately generous — we want to lean permissive at launch):
|
|
14
|
+
* files: MAX_INLINE_FILES entries, MAX_INLINE_TOTAL_BYTES total
|
|
15
|
+
* git_url: MAX_CLONE_BYTES on disk after `git clone --depth=1`
|
|
16
|
+
*
|
|
17
|
+
* Security:
|
|
18
|
+
* - Inline file paths are normalised + rejected if they contain ".." or
|
|
19
|
+
* start with "/" or contain NUL.
|
|
20
|
+
* - Symlinks are not honoured for inline files (we only write regular files).
|
|
21
|
+
* - Git clone uses `--depth=1` and `--single-branch` to bound bandwidth.
|
|
22
|
+
* - Both modes write to a fresh /tmp dir which is removed after tar.gz is read.
|
|
23
|
+
*/
|
|
24
|
+
import { execSync } from 'child_process';
|
|
25
|
+
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync, statSync, } from 'fs';
|
|
26
|
+
import { tmpdir } from 'os';
|
|
27
|
+
import { dirname, join, resolve, sep } from 'path';
|
|
28
|
+
/** Upper bound on inline file count (per single `deploy` call). */
|
|
29
|
+
export const MAX_INLINE_FILES = 200;
|
|
30
|
+
/** Upper bound on combined inline content length, in bytes. */
|
|
31
|
+
export const MAX_INLINE_TOTAL_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
32
|
+
/** Upper bound on a cloned git repo size (du -sb on the workdir), in bytes. */
|
|
33
|
+
export const MAX_CLONE_BYTES = 200 * 1024 * 1024; // 200 MB
|
|
34
|
+
/** Upper bound on path component count, to defeat absurd nesting attacks. */
|
|
35
|
+
const MAX_PATH_DEPTH = 32;
|
|
36
|
+
export class ArchiveBuilderError extends Error {
|
|
37
|
+
code;
|
|
38
|
+
constructor(code, message) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.code = code;
|
|
41
|
+
this.name = 'ArchiveBuilderError';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Pack a record of file paths → text content into a tar.gz Buffer.
|
|
46
|
+
*
|
|
47
|
+
* Each value is treated as UTF-8 text. Binary assets are not supported in
|
|
48
|
+
* this mode — use `archiveFromGitUrl` for that. The caller is responsible
|
|
49
|
+
* for including framework manifests (package.json, requirements.txt, or a
|
|
50
|
+
* Dockerfile) so the backend's auto-detection picks the right builder.
|
|
51
|
+
*/
|
|
52
|
+
export function archiveFromFiles(files) {
|
|
53
|
+
const entries = Object.entries(files);
|
|
54
|
+
if (entries.length === 0) {
|
|
55
|
+
throw new ArchiveBuilderError('empty_files', 'files map cannot be empty');
|
|
56
|
+
}
|
|
57
|
+
if (entries.length > MAX_INLINE_FILES) {
|
|
58
|
+
throw new ArchiveBuilderError('too_many_files', `files map has ${entries.length} entries (max ${MAX_INLINE_FILES})`);
|
|
59
|
+
}
|
|
60
|
+
let totalBytes = 0;
|
|
61
|
+
const sanitised = [];
|
|
62
|
+
for (const [rawPath, content] of entries) {
|
|
63
|
+
const safePath = sanitiseInlinePath(rawPath);
|
|
64
|
+
const buf = Buffer.from(content, 'utf8');
|
|
65
|
+
totalBytes += buf.byteLength;
|
|
66
|
+
if (totalBytes > MAX_INLINE_TOTAL_BYTES) {
|
|
67
|
+
throw new ArchiveBuilderError('inline_too_large', `combined content exceeds ${MAX_INLINE_TOTAL_BYTES} bytes`);
|
|
68
|
+
}
|
|
69
|
+
sanitised.push([safePath, buf]);
|
|
70
|
+
}
|
|
71
|
+
const workdir = mkdtempSync(join(tmpdir(), 'sota-inline-'));
|
|
72
|
+
try {
|
|
73
|
+
for (const [path, buf] of sanitised) {
|
|
74
|
+
const abs = join(workdir, path);
|
|
75
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
76
|
+
writeFileSync(abs, buf);
|
|
77
|
+
}
|
|
78
|
+
return tarGzWorkdir(workdir);
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
rmSync(workdir, { recursive: true, force: true });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* `git clone --depth=1` the given URL (optionally on a specified branch),
|
|
86
|
+
* then pack the resulting workdir into a tar.gz Buffer. Only public repos
|
|
87
|
+
* are supported in v1 — there is no credential plumbing.
|
|
88
|
+
*
|
|
89
|
+
* Rejects URLs that don't begin with https://, http://, git://, ssh://, or
|
|
90
|
+
* the scp-like git@host:path form, to keep the surface predictable.
|
|
91
|
+
*/
|
|
92
|
+
export function archiveFromGitUrl(gitUrl, gitBranch) {
|
|
93
|
+
const url = gitUrl.trim();
|
|
94
|
+
if (!isPlausibleGitURL(url)) {
|
|
95
|
+
throw new ArchiveBuilderError('bad_git_url', `git_url must look like a URL (https://, git://, ssh://, or git@host:path) — got: ${url.slice(0, 80)}`);
|
|
96
|
+
}
|
|
97
|
+
if (url.length > 2000) {
|
|
98
|
+
throw new ArchiveBuilderError('bad_git_url', 'git_url too long (max 2000 chars)');
|
|
99
|
+
}
|
|
100
|
+
const branch = gitBranch ? gitBranch.trim() : '';
|
|
101
|
+
if (branch && !/^[\w./\-]{1,255}$/.test(branch)) {
|
|
102
|
+
throw new ArchiveBuilderError('bad_git_branch', 'git_branch contains invalid characters or exceeds 255 chars');
|
|
103
|
+
}
|
|
104
|
+
const workdir = mkdtempSync(join(tmpdir(), 'sota-git-'));
|
|
105
|
+
try {
|
|
106
|
+
// Cap clone bandwidth/time. `--depth=1` cuts history to the tip commit.
|
|
107
|
+
// `--single-branch` avoids fetching all refs.
|
|
108
|
+
const args = ['clone', '--depth=1', '--single-branch'];
|
|
109
|
+
if (branch) {
|
|
110
|
+
args.push('--branch', branch);
|
|
111
|
+
}
|
|
112
|
+
args.push('--', url, workdir);
|
|
113
|
+
try {
|
|
114
|
+
execSync(`git ${args.map(shellQuote).join(' ')}`, {
|
|
115
|
+
stdio: 'pipe',
|
|
116
|
+
timeout: 120_000,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
121
|
+
throw new ArchiveBuilderError('git_clone_failed', `git clone failed (URL=${url.slice(0, 120)}${branch ? `, branch=${branch}` : ''}): ${detail.slice(0, 300)}`);
|
|
122
|
+
}
|
|
123
|
+
// Hard size cap: refuse repos that ballooned past MAX_CLONE_BYTES.
|
|
124
|
+
const sizeBytes = duBytes(workdir);
|
|
125
|
+
if (sizeBytes > MAX_CLONE_BYTES) {
|
|
126
|
+
throw new ArchiveBuilderError('clone_too_large', `cloned repo is ${(sizeBytes / 1024 / 1024).toFixed(1)} MB (max ${(MAX_CLONE_BYTES / 1024 / 1024).toFixed(0)} MB)`);
|
|
127
|
+
}
|
|
128
|
+
return tarGzWorkdir(workdir);
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
rmSync(workdir, { recursive: true, force: true });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Pack an existing local directory into a tar.gz Buffer using the same
|
|
136
|
+
* ignore rules as the directory-mode `deploy` (excludes .git, node_modules,
|
|
137
|
+
* .env, .DS_Store). Exported so the deploy tool can re-use a single
|
|
138
|
+
* code path for archive construction.
|
|
139
|
+
*/
|
|
140
|
+
export function archiveFromDirectory(dir) {
|
|
141
|
+
const abs = resolve(dir);
|
|
142
|
+
try {
|
|
143
|
+
statSync(abs);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
throw new ArchiveBuilderError('not_found', `Directory not found: ${abs}`);
|
|
147
|
+
}
|
|
148
|
+
return tarGzWorkdir(abs);
|
|
149
|
+
}
|
|
150
|
+
function tarGzWorkdir(workdir) {
|
|
151
|
+
const archivePath = join(tmpdir(), `sota-deploy-${Date.now()}-${process.pid}.tar.gz`);
|
|
152
|
+
try {
|
|
153
|
+
execSync(`tar -czf ${shellQuote(archivePath)} --exclude='.git' --exclude='node_modules' --exclude='.env' --exclude='.DS_Store' -C ${shellQuote(workdir)} .`, { stdio: 'pipe' });
|
|
154
|
+
return readFileSync(archivePath);
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
try {
|
|
158
|
+
rmSync(archivePath, { force: true });
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// best-effort cleanup
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function sanitiseInlinePath(raw) {
|
|
166
|
+
if (!raw || typeof raw !== 'string') {
|
|
167
|
+
throw new ArchiveBuilderError('bad_path', 'file path must be a non-empty string');
|
|
168
|
+
}
|
|
169
|
+
if (raw.includes('\0')) {
|
|
170
|
+
throw new ArchiveBuilderError('bad_path', 'file path contains NUL byte');
|
|
171
|
+
}
|
|
172
|
+
if (raw.length > 1024) {
|
|
173
|
+
throw new ArchiveBuilderError('bad_path', `file path too long (max 1024 chars): ${raw.slice(0, 80)}…`);
|
|
174
|
+
}
|
|
175
|
+
// Strip leading "./" and "/"; reject ".." segments.
|
|
176
|
+
let p = raw.replace(/^\.?\//, '').replace(/\\/g, '/');
|
|
177
|
+
if (p.startsWith('/')) {
|
|
178
|
+
p = p.slice(1);
|
|
179
|
+
}
|
|
180
|
+
if (p === '' || p === '.' || p === '..') {
|
|
181
|
+
throw new ArchiveBuilderError('bad_path', `invalid file path: ${raw}`);
|
|
182
|
+
}
|
|
183
|
+
const segments = p.split('/');
|
|
184
|
+
if (segments.length > MAX_PATH_DEPTH) {
|
|
185
|
+
throw new ArchiveBuilderError('bad_path', `path too deep (${segments.length} segments, max ${MAX_PATH_DEPTH}): ${raw}`);
|
|
186
|
+
}
|
|
187
|
+
for (const seg of segments) {
|
|
188
|
+
if (seg === '' || seg === '.' || seg === '..') {
|
|
189
|
+
throw new ArchiveBuilderError('bad_path', `invalid path segment in: ${raw}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return segments.join(sep);
|
|
193
|
+
}
|
|
194
|
+
function isPlausibleGitURL(s) {
|
|
195
|
+
if (s.startsWith('https://') || s.startsWith('http://'))
|
|
196
|
+
return true;
|
|
197
|
+
if (s.startsWith('git://'))
|
|
198
|
+
return true;
|
|
199
|
+
if (s.startsWith('ssh://'))
|
|
200
|
+
return true;
|
|
201
|
+
if (s.startsWith('git@') && s.includes(':'))
|
|
202
|
+
return true;
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
function shellQuote(arg) {
|
|
206
|
+
// Single-quote and escape any embedded single quotes for safe shell pasting.
|
|
207
|
+
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
208
|
+
}
|
|
209
|
+
function duBytes(dir) {
|
|
210
|
+
try {
|
|
211
|
+
const out = execSync(`du -sb ${shellQuote(dir)}`, { stdio: ['ignore', 'pipe', 'pipe'] })
|
|
212
|
+
.toString()
|
|
213
|
+
.trim();
|
|
214
|
+
const n = parseInt(out.split(/\s+/)[0] ?? '0', 10);
|
|
215
|
+
return Number.isFinite(n) ? n : 0;
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return 0;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=archive-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-builder.js","sourceRoot":"","sources":["../../src/lib/archive-builder.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/tools/deploy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/tools/deploy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAW1C,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,QAyIvE"}
|
package/dist/tools/deploy.js
CHANGED
|
@@ -1,52 +1,122 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
3
|
-
import { existsSync } from 'fs';
|
|
4
|
-
import { resolve } from 'path';
|
|
2
|
+
import { archiveFromDirectory, archiveFromFiles, archiveFromGitUrl, ArchiveBuilderError, MAX_INLINE_FILES, MAX_INLINE_TOTAL_BYTES, MAX_CLONE_BYTES, } from '../lib/archive-builder.js';
|
|
5
3
|
export function registerDeployTool(server, client) {
|
|
6
4
|
server.registerTool('deploy', {
|
|
7
|
-
description: `Deploy an application to sota.io.
|
|
5
|
+
description: `Deploy an application to sota.io. The platform auto-detects your framework and builds a Docker image automatically:
|
|
8
6
|
|
|
9
7
|
- Next.js: Detected via next.config.js/ts. Add output: 'standalone' to next.config for optimal builds.
|
|
10
8
|
- Node.js: Detected via package.json with a "start" script. Works with Express, Fastify, Koa, Hapi, etc.
|
|
11
9
|
- Python: Detected via requirements.txt or pyproject.toml. Works with Flask, FastAPI, Django.
|
|
12
10
|
- Custom Dockerfile: If a Dockerfile exists in the project root, it takes priority over auto-detection. Use this for Go, Rust, Java, or any other language. The EXPOSE directive in the Dockerfile is used to detect the app port automatically.
|
|
13
11
|
|
|
12
|
+
THREE WAYS to supply the source code — pick EXACTLY ONE:
|
|
13
|
+
|
|
14
|
+
1. **files** (inline source from AI):
|
|
15
|
+
Pass a map of relative paths to UTF-8 text content. Best when you've just
|
|
16
|
+
generated a small app in this conversation and want to deploy it without
|
|
17
|
+
any filesystem step. Up to ${MAX_INLINE_FILES} files, ${MAX_INLINE_TOTAL_BYTES / 1024 / 1024} MB total. Include
|
|
18
|
+
the framework manifest (package.json, requirements.txt, or Dockerfile)
|
|
19
|
+
so auto-detection works.
|
|
20
|
+
|
|
21
|
+
2. **git_url** (clone a public repo):
|
|
22
|
+
Pass an https://, git://, ssh://, or git@host:path URL. We shallow-clone
|
|
23
|
+
it (--depth=1 --single-branch) on the server and deploy. Optional
|
|
24
|
+
git_branch picks a non-default branch. Only public repos are supported in
|
|
25
|
+
v1. Max ${MAX_CLONE_BYTES / 1024 / 1024} MB after clone.
|
|
26
|
+
|
|
27
|
+
3. **directory** (local filesystem):
|
|
28
|
+
Pass an absolute path. Only works when the MCP client has filesystem
|
|
29
|
+
access (Claude Code / CLI; not Claude.ai web). Defaults to the current
|
|
30
|
+
working directory when omitted.
|
|
31
|
+
|
|
14
32
|
IMPORTANT: Your app MUST listen on the PORT environment variable. For auto-detected frameworks (Next.js, Node.js, Python) PORT is 8080. For custom Dockerfiles, the port is auto-detected from the EXPOSE directive (e.g. EXPOSE 3000 sets PORT=3000). If no EXPOSE is found, it defaults to 8080.
|
|
15
33
|
|
|
16
34
|
Every project includes a managed PostgreSQL 17 database. Six environment variables are auto-injected into your container — no manual database configuration needed: DATABASE_URL (full connection string), PGHOST, PGPORT, PGUSER, PGPASSWORD, and PGDATABASE. Libraries that follow libpq conventions (node-postgres, pgx, psycopg2, Django) pick up the PG* variables automatically with no configuration. If your app needs database migrations, run them on startup.
|
|
17
35
|
|
|
18
|
-
Deployments use blue-green strategy for zero downtime. The old container keeps running until the new one passes health checks (60s timeout). Use get-logs to monitor build progress. Files matching .gitignore and .
|
|
36
|
+
Deployments use blue-green strategy for zero downtime. The old container keeps running until the new one passes health checks (60s timeout). Use get-logs to monitor build progress. Files matching .gitignore, .git/, node_modules/, .env, and .DS_Store are excluded from the archive.`,
|
|
19
37
|
inputSchema: {
|
|
20
|
-
project_id: z.string().describe('Project ID (UUID) to deploy to. Use list-projects to find the ID'),
|
|
21
|
-
|
|
38
|
+
project_id: z.string().describe('Project ID (UUID) to deploy to. Use list-projects to find the ID.'),
|
|
39
|
+
files: z
|
|
40
|
+
.record(z.string(), z.string())
|
|
41
|
+
.optional()
|
|
42
|
+
.describe(`Map of relative file paths to UTF-8 text content. Use when generating an app inline (e.g. from a Claude.ai web conversation). Include a framework manifest so auto-detection works. Mutually exclusive with git_url and directory.`),
|
|
43
|
+
git_url: z
|
|
44
|
+
.string()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe('Public git repository URL to clone (https://, git://, ssh://, or git@host:path). Mutually exclusive with files and directory.'),
|
|
47
|
+
git_branch: z
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe('Optional branch name when using git_url. Defaults to the repository default branch.'),
|
|
51
|
+
directory: z
|
|
52
|
+
.string()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe('Absolute path to a local directory (only useful when the MCP client has filesystem access — e.g. Claude Code). Defaults to the current working directory when no other mode is given. Mutually exclusive with files and git_url.'),
|
|
22
55
|
},
|
|
23
56
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
24
|
-
}, async ({ project_id, directory }) => {
|
|
25
|
-
|
|
26
|
-
|
|
57
|
+
}, async ({ project_id, files, git_url, git_branch, directory }) => {
|
|
58
|
+
// Mutual-exclusivity check: at most one mode set. When zero are set we
|
|
59
|
+
// fall back to directory mode with cwd (legacy CLI behaviour).
|
|
60
|
+
const modesSet = [files !== undefined, git_url !== undefined, directory !== undefined].filter(Boolean).length;
|
|
61
|
+
if (modesSet > 1) {
|
|
62
|
+
return {
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: 'text',
|
|
66
|
+
text: 'Error: deploy accepts exactly one of `files`, `git_url`, or `directory`. Pick the mode that matches your environment (files for inline AI-generated code, git_url for a public repo, directory for a local path).',
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
isError: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (git_branch && !git_url) {
|
|
73
|
+
return {
|
|
74
|
+
content: [
|
|
75
|
+
{
|
|
76
|
+
type: 'text',
|
|
77
|
+
text: 'Error: `git_branch` only makes sense together with `git_url`.',
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
isError: true,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
let archiveBuffer;
|
|
84
|
+
let modeDescription;
|
|
85
|
+
try {
|
|
86
|
+
if (files !== undefined) {
|
|
87
|
+
archiveBuffer = archiveFromFiles(files);
|
|
88
|
+
modeDescription = `inline files (${Object.keys(files).length} entries)`;
|
|
89
|
+
}
|
|
90
|
+
else if (git_url !== undefined) {
|
|
91
|
+
archiveBuffer = archiveFromGitUrl(git_url, git_branch);
|
|
92
|
+
modeDescription = `git clone (${git_url}${git_branch ? ` @ ${git_branch}` : ''})`;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const dir = directory || process.cwd();
|
|
96
|
+
archiveBuffer = archiveFromDirectory(dir);
|
|
97
|
+
modeDescription = `directory (${dir})`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
102
|
+
const code = error instanceof ArchiveBuilderError ? ` [${error.code}]` : '';
|
|
27
103
|
return {
|
|
28
104
|
content: [
|
|
29
105
|
{
|
|
30
106
|
type: 'text',
|
|
31
|
-
text: `
|
|
107
|
+
text: `Deploy failed during archive construction${code}: ${detail}`,
|
|
32
108
|
},
|
|
33
109
|
],
|
|
34
110
|
isError: true,
|
|
35
111
|
};
|
|
36
112
|
}
|
|
37
113
|
try {
|
|
38
|
-
// Create tar.gz archive using system tar (excludes common ignores)
|
|
39
|
-
const archivePath = `/tmp/sota-deploy-${Date.now()}.tar.gz`;
|
|
40
|
-
execSync(`tar -czf ${archivePath} --exclude='.git' --exclude='node_modules' --exclude='.env' --exclude='.DS_Store' -C ${dir} .`, { stdio: 'pipe' });
|
|
41
|
-
const { readFileSync, unlinkSync } = await import('fs');
|
|
42
|
-
const archiveBuffer = readFileSync(archivePath);
|
|
43
|
-
unlinkSync(archivePath);
|
|
44
114
|
const deployment = await client.deploy(project_id, archiveBuffer);
|
|
45
115
|
return {
|
|
46
116
|
content: [
|
|
47
117
|
{
|
|
48
118
|
type: 'text',
|
|
49
|
-
text: `Deployment started:\n ID: ${deployment.id}\n Status: ${deployment.status}\n URL: ${deployment.url || 'pending'}\n\nUse get-logs to check build progress.`,
|
|
119
|
+
text: `Deployment started (${modeDescription}):\n ID: ${deployment.id}\n Status: ${deployment.status}\n URL: ${deployment.url || 'pending'}\n\nUse get-logs to check build progress.`,
|
|
50
120
|
},
|
|
51
121
|
],
|
|
52
122
|
};
|
|
@@ -56,7 +126,7 @@ Deployments use blue-green strategy for zero downtime. The old container keeps r
|
|
|
56
126
|
content: [
|
|
57
127
|
{
|
|
58
128
|
type: 'text',
|
|
59
|
-
text: `Deploy failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
129
|
+
text: `Deploy failed during upload: ${error instanceof Error ? error.message : String(error)}`,
|
|
60
130
|
},
|
|
61
131
|
],
|
|
62
132
|
isError: true,
|
package/dist/tools/deploy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../src/tools/deploy.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../src/tools/deploy.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/tools/projects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/tools/projects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,QA6EzE"}
|
package/dist/tools/projects.js
CHANGED
|
@@ -18,18 +18,27 @@ export function registerProjectTools(server, client) {
|
|
|
18
18
|
};
|
|
19
19
|
});
|
|
20
20
|
server.registerTool('create-project', {
|
|
21
|
-
description: 'Create a new project on sota.io. Each project automatically provisions: (1) a managed PostgreSQL 17 database accessible via the DATABASE_URL environment variable (auto-injected, no configuration needed), (2) PgBouncer connection pooling (pool size 20, max 100 clients), (3) automatic daily database backups with 7-day retention, (4) a live URL at https://{slug}.sota.io with automatic HTTPS via Let\'s Encrypt. The project slug is auto-generated from the name (lowercase, hyphens, max 63 chars) and is immutable after creation. Supported frameworks: Next.js, Node.js (Express/Fastify/Koa), Python (Flask/FastAPI/Django), or any language via custom Dockerfile. You can also add up to 5 custom domains per project with automatic HTTPS (via API: POST /v1/projects/:id/domains with {domain: "yourdomain.com"}). DNS: A record to 23.88.45.28 for apex domains, CNAME to {slug}.sota.io for subdomains.',
|
|
21
|
+
description: 'Create a new project on sota.io. Each project automatically provisions: (1) a managed PostgreSQL 17 database accessible via the DATABASE_URL environment variable (auto-injected, no configuration needed), (2) PgBouncer connection pooling (pool size 20, max 100 clients), (3) automatic daily database backups with 7-day retention, (4) a live URL at https://{slug}.sota.io with automatic HTTPS via Let\'s Encrypt. The project slug is auto-generated from the name (lowercase, hyphens, max 63 chars) and is immutable after creation. Supported frameworks: Next.js, Node.js (Express/Fastify/Koa), Python (Flask/FastAPI/Django), or any language via custom Dockerfile. You can also add up to 5 custom domains per project with automatic HTTPS (via API: POST /v1/projects/:id/domains with {domain: "yourdomain.com"}). DNS: A record to 23.88.45.28 for apex domains, CNAME to {slug}.sota.io for subdomains.\n\nOptionally associate the project with a public git repository at create-time by passing `git_url` (and optional `git_branch`). The association is informational — it shows up in the dashboard and the `sota deploy --git` CLI flag can default to it — but does NOT enable auto-deploy-on-push yet.',
|
|
22
22
|
inputSchema: {
|
|
23
23
|
name: z.string().describe('Name for the new project. A URL slug will be auto-generated (e.g. "My Cool App" becomes my-cool-app.sota.io)'),
|
|
24
|
+
git_url: z.string().optional().describe('Optional git repository URL to associate with this project (https://, git://, ssh://, or git@host:path). Informational only — does not trigger an automatic deploy.'),
|
|
25
|
+
git_branch: z.string().optional().describe('Optional branch name (defaults to the repository default branch when omitted). Only meaningful when `git_url` is also set.'),
|
|
24
26
|
},
|
|
25
27
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
26
|
-
}, async ({ name }) => {
|
|
27
|
-
const project = await client.createProject({
|
|
28
|
+
}, async ({ name, git_url, git_branch }) => {
|
|
29
|
+
const project = await client.createProject({
|
|
30
|
+
name,
|
|
31
|
+
git_url,
|
|
32
|
+
git_branch,
|
|
33
|
+
});
|
|
34
|
+
const gitLine = project.git_url
|
|
35
|
+
? `\n Git: ${project.git_url}${project.git_branch ? ` @ ${project.git_branch}` : ''}`
|
|
36
|
+
: '';
|
|
28
37
|
return {
|
|
29
38
|
content: [
|
|
30
39
|
{
|
|
31
40
|
type: 'text',
|
|
32
|
-
text: `Project created:\n Name: ${project.name}\n Slug: ${project.slug}\n ID: ${project.id}\n URL: https://${project.slug}.sota.io`,
|
|
41
|
+
text: `Project created:\n Name: ${project.name}\n Slug: ${project.slug}\n ID: ${project.id}\n URL: https://${project.slug}.sota.io${gitLine}`,
|
|
33
42
|
},
|
|
34
43
|
],
|
|
35
44
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/tools/projects.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,UAAU,oBAAoB,CAAC,MAAiB,EAAE,MAAkB;IACxE,MAAM,CAAC,YAAY,CAAC,eAAe,EAAE;QACnC,WAAW,EAAE,6aAA6a;QAC1b,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE;KACxG,EAAE,KAAK,IAAI,EAAE;QACZ,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CACxB,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,EAAE,EAAE,CAC7C,CAAC;QACF,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,QAAQ,CAAC,MAAM,KAAK,CAAC;wBACzB,CAAC,CAAC,sDAAsD;wBACxD,CAAC,CAAC,cAAc,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;iBACrC;aACF;SACF,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,YAAY,CAAC,gBAAgB,EAAE;QACpC,WAAW,EAAE
|
|
1
|
+
{"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/tools/projects.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sota-io/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"mcpName": "io.sota/mcp",
|
|
5
|
-
"description": "MCP server for sota.io
|
|
5
|
+
"description": "MCP server for sota.io — EU-native DevOps PaaS. Deploy web apps via AI agents with managed PostgreSQL, zero-downtime deploys, and automatic HTTPS. Supports Next.js, Node.js, Python, and custom Dockerfiles.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"bin": {
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"start": "node dist/index.js",
|
|
19
19
|
"dev": "tsx src/index.ts",
|
|
20
20
|
"prepublishOnly": "npm run build",
|
|
21
|
-
"start:http": "node dist/http.js"
|
|
21
|
+
"start:http": "node dist/http.js",
|
|
22
|
+
"test": "npm run build && node --test test/*.test.mjs"
|
|
22
23
|
},
|
|
23
24
|
"repository": {
|
|
24
25
|
"type": "git",
|
|
@@ -40,21 +41,21 @@
|
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
43
|
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
43
|
-
"@sota-io/sdk": "^1.
|
|
44
|
-
"zod": "^3.25.0",
|
|
44
|
+
"@sota-io/sdk": "^1.3.0",
|
|
45
45
|
"express": "^4.21.0",
|
|
46
46
|
"jose": "^5.9.0",
|
|
47
|
-
"pg": "^8.13.0"
|
|
47
|
+
"pg": "^8.13.0",
|
|
48
|
+
"zod": "^3.25.0"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
51
|
+
"@types/express": "^4.17.21",
|
|
50
52
|
"@types/node": "^22.0.0",
|
|
53
|
+
"@types/pg": "^8.11.0",
|
|
51
54
|
"tsx": "^4.0.0",
|
|
52
|
-
"typescript": "^5.7.0"
|
|
53
|
-
"@types/express": "^4.17.21",
|
|
54
|
-
"@types/pg": "^8.11.0"
|
|
55
|
+
"typescript": "^5.7.0"
|
|
55
56
|
},
|
|
56
57
|
"engines": {
|
|
57
58
|
"node": ">=20.0.0"
|
|
58
59
|
},
|
|
59
60
|
"license": "MIT"
|
|
60
|
-
}
|
|
61
|
+
}
|