@jetpump/mcp 0.1.0-beta.1
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 +39 -0
- package/dist/handlers.js +130 -0
- package/dist/index.js +19 -0
- package/dist/logger.js +42 -0
- package/dist/tools.js +32 -0
- package/dist/zip.js +184 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @jetpump/mcp
|
|
2
|
+
|
|
3
|
+
MCP server that ships the current directory to a live `*.jetpump.dev` URL in
|
|
4
|
+
one command from Claude Code.
|
|
5
|
+
|
|
6
|
+
> **Beta.** Test-mode billing only. Free tier allows 1 active deploy with a
|
|
7
|
+
> 48-hour TTL. See https://jetpump.dev for tiers.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
claude mcp add @jetpump/mcp@beta
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Use
|
|
16
|
+
|
|
17
|
+
In any project directory, ask Claude Code:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
deploy this project
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Jetpump detects your stack (Next.js, Vite, React, Node, Python, Docker, …),
|
|
24
|
+
builds it, and serves it on a live URL under `*.jetpump.dev`.
|
|
25
|
+
|
|
26
|
+
## Tools exposed
|
|
27
|
+
|
|
28
|
+
- `deploy` — zip current directory, upload, build, deploy
|
|
29
|
+
- `list_deploys` — list your active deploys
|
|
30
|
+
- `teardown` — delete a deploy and free its subdomain
|
|
31
|
+
|
|
32
|
+
## Auth
|
|
33
|
+
|
|
34
|
+
First call prompts for an email address, sends a magic link via Resend, and
|
|
35
|
+
stores a JWT locally.
|
|
36
|
+
|
|
37
|
+
## License
|
|
38
|
+
|
|
39
|
+
MIT
|
package/dist/handlers.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { zipDirectory } from './zip.js';
|
|
5
|
+
const API_URL = process.env.JETPUMP_API_URL;
|
|
6
|
+
const SECRET = process.env.JETPUMP_SECRET;
|
|
7
|
+
function headers(extra = {}) {
|
|
8
|
+
return {
|
|
9
|
+
'x-poc-secret': SECRET ?? '',
|
|
10
|
+
'content-type': 'application/json',
|
|
11
|
+
...extra,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function assertConfigured() {
|
|
15
|
+
if (!API_URL || !SECRET) {
|
|
16
|
+
throw new Error('JETPUMP_API_URL and JETPUMP_SECRET must be set');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function handleDeploy(args) {
|
|
20
|
+
assertConfigured();
|
|
21
|
+
const dir = path.resolve(args.directory);
|
|
22
|
+
const zipPath = path.join(os.tmpdir(), `jetpump-${Date.now()}.zip`);
|
|
23
|
+
console.error(`[jetpump-mcp] deploy start dir=${dir}`);
|
|
24
|
+
let zipInfo;
|
|
25
|
+
try {
|
|
26
|
+
zipInfo = await zipDirectory(dir, zipPath);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
const msg = err.message;
|
|
30
|
+
console.error(`[jetpump-mcp] deploy failed at zip: ${msg}`);
|
|
31
|
+
throw new Error(`zip failed: ${msg}`);
|
|
32
|
+
}
|
|
33
|
+
const { size, fileCount } = zipInfo;
|
|
34
|
+
let presign;
|
|
35
|
+
try {
|
|
36
|
+
const res = await fetch(`${API_URL}/presign`, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: headers(),
|
|
39
|
+
body: JSON.stringify({ size_bytes: size }),
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok)
|
|
42
|
+
throw new Error(`HTTP ${res.status} ${await res.text().catch(() => '')}`);
|
|
43
|
+
presign = (await res.json());
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const msg = err.message;
|
|
47
|
+
console.error(`[jetpump-mcp] deploy failed at presign: ${msg}`);
|
|
48
|
+
throw new Error(`presign failed: ${msg}`);
|
|
49
|
+
}
|
|
50
|
+
console.error(`[jetpump-mcp] presign ok deployment_id=${presign.deployment_id} files=${fileCount} zip=${size}`);
|
|
51
|
+
try {
|
|
52
|
+
const zipBuf = await fs.readFile(zipPath);
|
|
53
|
+
const uploadRes = await fetch(presign.upload_url, {
|
|
54
|
+
method: 'PUT',
|
|
55
|
+
headers: { 'content-type': 'application/zip' },
|
|
56
|
+
body: zipBuf,
|
|
57
|
+
});
|
|
58
|
+
if (!uploadRes.ok) {
|
|
59
|
+
throw new Error(`HTTP ${uploadRes.status} ${await uploadRes.text().catch(() => '')}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
const msg = err.message;
|
|
64
|
+
console.error(`[jetpump-mcp] deploy failed at upload: ${msg}`);
|
|
65
|
+
throw new Error(`upload failed: ${msg}`);
|
|
66
|
+
}
|
|
67
|
+
console.error(`[jetpump-mcp] upload ok deployment_id=${presign.deployment_id}`);
|
|
68
|
+
try {
|
|
69
|
+
const res = await fetch(`${API_URL}/deploy`, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: headers(),
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
deployment_id: presign.deployment_id,
|
|
74
|
+
object_path: presign.object_path,
|
|
75
|
+
project_name: path.basename(dir),
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
78
|
+
if (!res.ok) {
|
|
79
|
+
throw new Error(`HTTP ${res.status} ${await res.text().catch(() => '')}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
const msg = err.message;
|
|
84
|
+
console.error(`[jetpump-mcp] deploy failed at trigger: ${msg}`);
|
|
85
|
+
throw new Error(`trigger failed: ${msg}`);
|
|
86
|
+
}
|
|
87
|
+
console.error(`[jetpump-mcp] build triggered deployment_id=${presign.deployment_id}`);
|
|
88
|
+
const final = await pollUntilReady(presign.deployment_id);
|
|
89
|
+
console.error(`[jetpump-mcp] deploy ${final.status} id=${final.deployment_id} url=${final.url ?? '-'}`);
|
|
90
|
+
return { content: [{ type: 'text', text: formatResult(final) }] };
|
|
91
|
+
}
|
|
92
|
+
export async function handleListDeploys() {
|
|
93
|
+
assertConfigured();
|
|
94
|
+
console.error('[jetpump-mcp] list_deploys');
|
|
95
|
+
const res = await fetch(`${API_URL}/deploys`, { headers: headers() }).then((r) => r.json());
|
|
96
|
+
return { content: [{ type: 'text', text: JSON.stringify(res.deploys, null, 2) }] };
|
|
97
|
+
}
|
|
98
|
+
export async function handleTeardown(args) {
|
|
99
|
+
assertConfigured();
|
|
100
|
+
console.error(`[jetpump-mcp] teardown id=${args.deployment_id}`);
|
|
101
|
+
const res = await fetch(`${API_URL}/deploy/${args.deployment_id}`, {
|
|
102
|
+
method: 'DELETE',
|
|
103
|
+
headers: headers(),
|
|
104
|
+
}).then((r) => r.json());
|
|
105
|
+
return { content: [{ type: 'text', text: JSON.stringify(res, null, 2) }] };
|
|
106
|
+
}
|
|
107
|
+
async function pollUntilReady(deploymentId) {
|
|
108
|
+
const deadline = Date.now() + 10 * 60 * 1000;
|
|
109
|
+
while (Date.now() < deadline) {
|
|
110
|
+
const res = (await fetch(`${API_URL}/deploy/${deploymentId}`, {
|
|
111
|
+
headers: headers(),
|
|
112
|
+
}).then((r) => r.json()));
|
|
113
|
+
if (res.status === 'ready' || res.status === 'failed')
|
|
114
|
+
return res;
|
|
115
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
116
|
+
}
|
|
117
|
+
throw new Error(`Deploy ${deploymentId} timed out`);
|
|
118
|
+
}
|
|
119
|
+
function formatResult(d) {
|
|
120
|
+
if (d.status === 'failed') {
|
|
121
|
+
return `Deploy ${d.deployment_id} failed: ${d.error ?? 'unknown error'}`;
|
|
122
|
+
}
|
|
123
|
+
const secs = Math.round((d.build_ms ?? 0) / 1000);
|
|
124
|
+
return [
|
|
125
|
+
`Live at ${d.url}`,
|
|
126
|
+
` Stack: ${d.stack ?? 'unknown'}`,
|
|
127
|
+
` Build: ${secs}s`,
|
|
128
|
+
` ID: ${d.deployment_id}`,
|
|
129
|
+
].join('\n');
|
|
130
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import './logger.js';
|
|
3
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
import { tools } from './tools.js';
|
|
7
|
+
import { handleDeploy, handleListDeploys, handleTeardown } from './handlers.js';
|
|
8
|
+
const server = new Server({ name: 'jetpump', version: '0.0.1' }, { capabilities: { tools: {} } });
|
|
9
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
10
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
11
|
+
const { name, arguments: args } = req.params;
|
|
12
|
+
switch (name) {
|
|
13
|
+
case 'deploy': return handleDeploy(args);
|
|
14
|
+
case 'list_deploys': return handleListDeploys();
|
|
15
|
+
case 'teardown': return handleTeardown(args);
|
|
16
|
+
default: throw new Error(`Unknown tool: ${name}`);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
await server.connect(new StdioServerTransport());
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
// Persistent stderr log: tees every console.error line to ~/.jetpump/mcp.log
|
|
5
|
+
// so that [jetpump-mcp] breadcrumbs survive a GUI-launched Claude Code (which
|
|
6
|
+
// has no terminal to capture the MCP child's stderr). POC-grade: appends
|
|
7
|
+
// forever, no rotation. MVP should cap size and rotate.
|
|
8
|
+
const LOG_DIR = path.join(os.homedir(), '.jetpump');
|
|
9
|
+
const LOG_FILE = path.join(LOG_DIR, 'mcp.log');
|
|
10
|
+
let logStream = null;
|
|
11
|
+
try {
|
|
12
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
13
|
+
logStream = fs.createWriteStream(LOG_FILE, { flags: 'a' });
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// If the log file can't be opened, fall back to stderr-only.
|
|
17
|
+
logStream = null;
|
|
18
|
+
}
|
|
19
|
+
const origError = console.error.bind(console);
|
|
20
|
+
console.error = (...args) => {
|
|
21
|
+
origError(...args);
|
|
22
|
+
if (!logStream)
|
|
23
|
+
return;
|
|
24
|
+
try {
|
|
25
|
+
const line = args
|
|
26
|
+
.map((a) => (typeof a === 'string' ? a : safeStringify(a)))
|
|
27
|
+
.join(' ');
|
|
28
|
+
logStream.write(`${new Date().toISOString()} ${line}\n`);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// swallow — we already wrote to real stderr
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
function safeStringify(v) {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.stringify(v);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return String(v);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
console.error(`[jetpump-mcp] boot pid=${process.pid} node=${process.version}`);
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const tools = [
|
|
2
|
+
{
|
|
3
|
+
name: 'deploy',
|
|
4
|
+
description: 'Zip a local directory and deploy it to Jetpump. Returns a live URL.',
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: 'object',
|
|
7
|
+
properties: {
|
|
8
|
+
directory: {
|
|
9
|
+
type: 'string',
|
|
10
|
+
description: 'Absolute path to the project directory to deploy',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
required: ['directory'],
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'list_deploys',
|
|
18
|
+
description: 'List active Jetpump deployments.',
|
|
19
|
+
inputSchema: { type: 'object', properties: {} },
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'teardown',
|
|
23
|
+
description: 'Delete a Jetpump deployment by ID.',
|
|
24
|
+
inputSchema: {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
deployment_id: { type: 'string', description: 'Jetpump deployment ID (e.g. dep_a3b4c5d6)' },
|
|
28
|
+
},
|
|
29
|
+
required: ['deployment_id'],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
];
|
package/dist/zip.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { createWriteStream } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import archiver from 'archiver';
|
|
6
|
+
// The `ignore` package has an awkward export shape (function+namespace) that
|
|
7
|
+
// TypeScript's NodeNext resolution can't type-check cleanly. createRequire
|
|
8
|
+
// sidesteps the import typing while still getting the correct runtime value.
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const ignore = require('ignore');
|
|
11
|
+
const ALWAYS_EXCLUDE = [
|
|
12
|
+
// VCS and editor
|
|
13
|
+
'.git',
|
|
14
|
+
'.hg',
|
|
15
|
+
'.svn',
|
|
16
|
+
'.idea',
|
|
17
|
+
'.vscode',
|
|
18
|
+
'.DS_Store',
|
|
19
|
+
'*.log',
|
|
20
|
+
// Node/JS build + cache
|
|
21
|
+
'node_modules',
|
|
22
|
+
'dist',
|
|
23
|
+
'build',
|
|
24
|
+
'out',
|
|
25
|
+
'.next',
|
|
26
|
+
'.nuxt',
|
|
27
|
+
'.svelte-kit',
|
|
28
|
+
'.turbo',
|
|
29
|
+
'.parcel-cache',
|
|
30
|
+
'.cache',
|
|
31
|
+
'.nyc_output',
|
|
32
|
+
'coverage',
|
|
33
|
+
// Python build + cache
|
|
34
|
+
'.venv',
|
|
35
|
+
'venv',
|
|
36
|
+
'env',
|
|
37
|
+
'__pycache__',
|
|
38
|
+
'.pytest_cache',
|
|
39
|
+
'.mypy_cache',
|
|
40
|
+
'.ruff_cache',
|
|
41
|
+
'.tox',
|
|
42
|
+
'.ipynb_checkpoints',
|
|
43
|
+
// ML / data caches (the SQViewer killer)
|
|
44
|
+
'.semantic-cache',
|
|
45
|
+
'models',
|
|
46
|
+
// Rust / Java / Go build
|
|
47
|
+
'target',
|
|
48
|
+
'.gradle',
|
|
49
|
+
'.m2',
|
|
50
|
+
// IaC
|
|
51
|
+
'.terraform',
|
|
52
|
+
'.vagrant',
|
|
53
|
+
];
|
|
54
|
+
// Hard ceiling on total source bytes that may enter the zip. Jetpump is a
|
|
55
|
+
// deploy tool — projects larger than this almost always contain unintended
|
|
56
|
+
// artifacts (ML weights, datasets, caches) and zipping them would OOM the
|
|
57
|
+
// MCP server process and sever the stdio transport with JSON-RPC -32000.
|
|
58
|
+
export const MAX_ZIP_SOURCE_BYTES = 500 * 1024 * 1024;
|
|
59
|
+
export async function zipDirectory(sourceDir, outPath) {
|
|
60
|
+
const started = Date.now();
|
|
61
|
+
const ig = ignore().add(ALWAYS_EXCLUDE);
|
|
62
|
+
try {
|
|
63
|
+
const gitignore = await fs.readFile(path.join(sourceDir, '.gitignore'), 'utf8');
|
|
64
|
+
ig.add(gitignore);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// no .gitignore → only ALWAYS_EXCLUDE applies
|
|
68
|
+
}
|
|
69
|
+
console.error(`[jetpump-mcp] scan start dir=${sourceDir}`);
|
|
70
|
+
const scan = await walk(sourceDir, ig);
|
|
71
|
+
console.error(`[jetpump-mcp] scan done files=${scan.files.length} bytes=${scan.totalBytes} (${fmtBytes(scan.totalBytes)})`);
|
|
72
|
+
if (scan.totalBytes > MAX_ZIP_SOURCE_BYTES) {
|
|
73
|
+
const top = scan.topDirs
|
|
74
|
+
.slice(0, 5)
|
|
75
|
+
.map((d) => ` ${d.name}: ${fmtBytes(d.bytes)}`)
|
|
76
|
+
.join('\n');
|
|
77
|
+
const msg = `Refusing to zip ${fmtBytes(scan.totalBytes)} from ${sourceDir} — ` +
|
|
78
|
+
`exceeds the ${fmtBytes(MAX_ZIP_SOURCE_BYTES)} source-size limit.\n\n` +
|
|
79
|
+
`Largest included paths:\n${top}\n\n` +
|
|
80
|
+
`Add the heaviest directories to your project's .gitignore and retry. ` +
|
|
81
|
+
`Jetpump already ignores node_modules, .venv, .cache, .semantic-cache, models, ` +
|
|
82
|
+
`dist, build, .next, __pycache__, target, and common IDE/VCS directories by default.`;
|
|
83
|
+
console.error(`[jetpump-mcp] size guard tripped: ${fmtBytes(scan.totalBytes)}`);
|
|
84
|
+
throw new Error(msg);
|
|
85
|
+
}
|
|
86
|
+
console.error(`[jetpump-mcp] zip start out=${outPath}`);
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
let settled = false;
|
|
89
|
+
const settle = (fn) => {
|
|
90
|
+
if (settled)
|
|
91
|
+
return;
|
|
92
|
+
settled = true;
|
|
93
|
+
fn();
|
|
94
|
+
};
|
|
95
|
+
const output = createWriteStream(outPath);
|
|
96
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
97
|
+
output.on('close', () => {
|
|
98
|
+
const ms = Date.now() - started;
|
|
99
|
+
const zipSize = archive.pointer();
|
|
100
|
+
console.error(`[jetpump-mcp] zip done in=${fmtBytes(scan.totalBytes)} out=${fmtBytes(zipSize)} ms=${ms}`);
|
|
101
|
+
settle(() => resolve({ size: zipSize, fileCount: scan.files.length, bytesIn: scan.totalBytes }));
|
|
102
|
+
});
|
|
103
|
+
output.on('error', (err) => {
|
|
104
|
+
console.error(`[jetpump-mcp] zip output error: ${err.message}`);
|
|
105
|
+
settle(() => reject(err));
|
|
106
|
+
});
|
|
107
|
+
archive.on('error', (err) => {
|
|
108
|
+
console.error(`[jetpump-mcp] zip archive error: ${err.message}`);
|
|
109
|
+
settle(() => reject(err));
|
|
110
|
+
});
|
|
111
|
+
archive.on('warning', (err) => {
|
|
112
|
+
if (err?.code === 'ENOENT') {
|
|
113
|
+
console.error(`[jetpump-mcp] zip skipping missing file: ${err.message}`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
console.error(`[jetpump-mcp] zip warning: ${err?.message}`);
|
|
117
|
+
settle(() => reject(err));
|
|
118
|
+
});
|
|
119
|
+
archive.pipe(output);
|
|
120
|
+
for (const rel of scan.files) {
|
|
121
|
+
archive.file(path.join(sourceDir, rel), { name: rel });
|
|
122
|
+
}
|
|
123
|
+
archive.finalize().catch((err) => {
|
|
124
|
+
console.error(`[jetpump-mcp] zip finalize error: ${err?.message ?? err}`);
|
|
125
|
+
settle(() => reject(err));
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
async function walk(root, ig) {
|
|
130
|
+
const files = [];
|
|
131
|
+
const dirBytes = new Map();
|
|
132
|
+
let totalBytes = 0;
|
|
133
|
+
async function visit(current) {
|
|
134
|
+
let entries;
|
|
135
|
+
try {
|
|
136
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
console.error(`[jetpump-mcp] readdir failed ${current}: ${err.message}`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
for (const entry of entries) {
|
|
143
|
+
const abs = path.join(current, entry.name);
|
|
144
|
+
const rel = path.relative(root, abs);
|
|
145
|
+
if (!rel)
|
|
146
|
+
continue;
|
|
147
|
+
const relForIgnore = entry.isDirectory() ? `${rel}/` : rel;
|
|
148
|
+
if (ig.ignores(relForIgnore))
|
|
149
|
+
continue;
|
|
150
|
+
if (entry.isDirectory()) {
|
|
151
|
+
await visit(abs);
|
|
152
|
+
}
|
|
153
|
+
else if (entry.isFile()) {
|
|
154
|
+
let size = 0;
|
|
155
|
+
try {
|
|
156
|
+
const stat = await fs.stat(abs);
|
|
157
|
+
size = stat.size;
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
console.error(`[jetpump-mcp] stat failed ${abs}: ${err.message}`);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
files.push(rel);
|
|
164
|
+
totalBytes += size;
|
|
165
|
+
const topDir = rel.split(path.sep)[0];
|
|
166
|
+
dirBytes.set(topDir, (dirBytes.get(topDir) ?? 0) + size);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
await visit(root);
|
|
171
|
+
const topDirs = [...dirBytes.entries()]
|
|
172
|
+
.map(([name, bytes]) => ({ name, bytes }))
|
|
173
|
+
.sort((a, b) => b.bytes - a.bytes);
|
|
174
|
+
return { files, totalBytes, topDirs };
|
|
175
|
+
}
|
|
176
|
+
function fmtBytes(n) {
|
|
177
|
+
if (n < 1024)
|
|
178
|
+
return `${n} B`;
|
|
179
|
+
if (n < 1024 * 1024)
|
|
180
|
+
return `${(n / 1024).toFixed(1)} KB`;
|
|
181
|
+
if (n < 1024 * 1024 * 1024)
|
|
182
|
+
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
183
|
+
return `${(n / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
184
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jetpump/mcp",
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"description": "Jetpump MCP server — deploy the current directory from Claude Code.",
|
|
5
|
+
"homepage": "https://jetpump.dev",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/AJB93/jetpump.git",
|
|
10
|
+
"directory": "packages/mcp"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "dist/index.js",
|
|
14
|
+
"bin": { "jetpump-mcp": "dist/index.js" },
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"engines": { "node": ">=20" },
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"dev": "tsc --watch",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
27
|
+
"archiver": "^7.0.1",
|
|
28
|
+
"ignore": "^6.0.2"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/archiver": "^6.0.3",
|
|
32
|
+
"@types/node": "^22.9.0",
|
|
33
|
+
"typescript": "^5.6.3"
|
|
34
|
+
}
|
|
35
|
+
}
|