@shipstatic/mcp 0.5.1 → 0.7.0-beta.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 +4 -4
- package/dist/call.js +6 -6
- package/dist/index.js +7 -2
- package/dist/server.d.ts +1 -1
- package/dist/server.js +72 -23
- package/package.json +10 -6
package/README.md
CHANGED
|
@@ -83,14 +83,14 @@ Add to `~/.gemini/antigravity/mcp_config.json`:
|
|
|
83
83
|
|
|
84
84
|
Same config format — `npx @shipstatic/mcp`. Works with any MCP-compatible client.
|
|
85
85
|
|
|
86
|
-
## Free
|
|
86
|
+
## Free token — permanent deployments
|
|
87
87
|
|
|
88
|
-
`
|
|
88
|
+
`SHIP_TOKEN` is optional. Without it, deploys behave like the hosted endpoint (public, claim URL, 3-day expiry). With it, you get permanent deployments, the full toolset, and bigger limits.
|
|
89
89
|
|
|
90
|
-
Get a free
|
|
90
|
+
Get a free token at [my.shipstatic.com/api-key](https://my.shipstatic.com/api-key):
|
|
91
91
|
|
|
92
92
|
```bash
|
|
93
|
-
claude mcp add shipstatic -e
|
|
93
|
+
claude mcp add shipstatic -e SHIP_TOKEN=ship-... -- npx @shipstatic/mcp
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
## Tools
|
package/dist/call.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ErrorType, isShipError } from '@shipstatic/ship';
|
|
2
2
|
export async function call(fn) {
|
|
3
3
|
try {
|
|
4
4
|
const result = await fn();
|
|
5
|
-
const text = result === undefined
|
|
6
|
-
? 'Done.'
|
|
7
|
-
: JSON.stringify(result, null, 2);
|
|
5
|
+
const text = result === undefined ? 'Done.' : JSON.stringify(result, null, 2);
|
|
8
6
|
return { content: [{ type: 'text', text }] };
|
|
9
7
|
}
|
|
10
8
|
catch (error) {
|
|
@@ -15,10 +13,12 @@ function handleError(error) {
|
|
|
15
13
|
if (isShipError(error)) {
|
|
16
14
|
let message = error.message;
|
|
17
15
|
if (error.isType(ErrorType.Authentication)) {
|
|
18
|
-
message +=
|
|
16
|
+
message +=
|
|
17
|
+
'\n\nHint: Set a free SHIP_TOKEN environment variable in your MCP server configuration.';
|
|
19
18
|
}
|
|
20
19
|
if (error.isType(ErrorType.Forbidden)) {
|
|
21
|
-
message +=
|
|
20
|
+
message +=
|
|
21
|
+
'\n\nHint: This action is not permitted. Likely cause: plan limits reached or the account is terminated. Stop retrying — the user needs to upgrade or contact support at https://my.shipstatic.com.';
|
|
22
22
|
}
|
|
23
23
|
if (error.isType(ErrorType.Validation) && error.details) {
|
|
24
24
|
message += `\n\nDetails: ${safeStringify(error.details)}`;
|
package/dist/index.js
CHANGED
|
@@ -3,9 +3,14 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
3
3
|
import Ship from '@shipstatic/ship';
|
|
4
4
|
import { createServer } from './server.js';
|
|
5
5
|
async function main() {
|
|
6
|
-
//
|
|
6
|
+
// SHIP_TOKEN is optional — without it, deployments are public (3-day expiry).
|
|
7
7
|
// The SDK coerces empty strings to undefined, so we can pass through directly.
|
|
8
|
-
|
|
8
|
+
//
|
|
9
|
+
// One credential slot, any platform token: the value's prefix says what it
|
|
10
|
+
// is (`ship-` API key, `deploy-` deploy token, anything else an opaque
|
|
11
|
+
// bearer) and the server classifies it. MCP never has to know which kind it
|
|
12
|
+
// holds.
|
|
13
|
+
const ship = new Ship({ token: process.env.SHIP_TOKEN });
|
|
9
14
|
const server = createServer(ship);
|
|
10
15
|
const transport = new StdioServerTransport();
|
|
11
16
|
await server.connect(transport);
|
package/dist/server.d.ts
CHANGED
package/dist/server.js
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
|
-
import { LABEL_CONSTRAINTS, PASSWORD_CONSTRAINTS } from '@shipstatic/ship';
|
|
2
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
1
|
import { createRequire } from 'node:module';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { LABEL_CONSTRAINTS, PASSWORD_CONSTRAINTS } from '@shipstatic/ship';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { call } from './call.js';
|
|
6
6
|
const { version } = createRequire(import.meta.url)('../package.json');
|
|
7
7
|
const OPEN_WORLD = { openWorldHint: true };
|
|
8
|
-
const READ = {
|
|
8
|
+
const READ = {
|
|
9
|
+
readOnlyHint: true,
|
|
10
|
+
destructiveHint: false,
|
|
11
|
+
idempotentHint: true,
|
|
12
|
+
...OPEN_WORLD,
|
|
13
|
+
};
|
|
9
14
|
const CREATE = { readOnlyHint: false, destructiveHint: false, ...OPEN_WORLD };
|
|
10
|
-
const WRITE = {
|
|
11
|
-
|
|
15
|
+
const WRITE = {
|
|
16
|
+
readOnlyHint: false,
|
|
17
|
+
destructiveHint: false,
|
|
18
|
+
idempotentHint: true,
|
|
19
|
+
...OPEN_WORLD,
|
|
20
|
+
};
|
|
21
|
+
const DESTRUCTIVE = {
|
|
22
|
+
readOnlyHint: false,
|
|
23
|
+
destructiveHint: true,
|
|
24
|
+
idempotentHint: true,
|
|
25
|
+
...OPEN_WORLD,
|
|
26
|
+
};
|
|
12
27
|
const INSTRUCTIONS = `ShipStatic deploys static websites instantly. Free, no account required.
|
|
13
28
|
|
|
14
29
|
To deploy: call deployments_upload with the build output directory path. The site is live immediately. To make the site private, pass \`password\` — visitors must unlock before viewing, including on any custom domains pointing at it.
|
|
15
30
|
|
|
16
|
-
Without
|
|
31
|
+
Without SHIP_TOKEN, deployments are public and expire in 3 days. The response includes a claim URL — always show the deployment URL and the claim URL to the user so they can keep the site permanently.
|
|
17
32
|
|
|
18
|
-
With
|
|
33
|
+
With SHIP_TOKEN configured, deployments go to the user's account and never expire. Listing, managing, and domain operations also require SHIP_TOKEN.
|
|
19
34
|
|
|
20
35
|
Concepts:
|
|
21
36
|
- Deployment: an immutable set of files with an instant URL (e.g. happy-cat-abc1234.shipstatic.com). No setup needed.
|
|
@@ -31,12 +46,20 @@ export function createServer(ship) {
|
|
|
31
46
|
});
|
|
32
47
|
// Deployments
|
|
33
48
|
server.registerTool('deployments_upload', {
|
|
34
|
-
description: 'Deploy a static site instantly — free, no account or API key required. Returns the live URL, file count, and size. Without
|
|
49
|
+
description: 'Deploy a static site instantly — free, no account or API key required. Returns the live URL, file count, and size. Without SHIP_TOKEN, the response includes a claim URL (site expires in 3 days) — always show both the deployment URL and claim URL to the user. To make the site private, pass `password`; always show the password to the user if you set one.',
|
|
35
50
|
annotations: CREATE,
|
|
36
51
|
inputSchema: {
|
|
37
|
-
path: z
|
|
38
|
-
|
|
39
|
-
|
|
52
|
+
path: z
|
|
53
|
+
.string()
|
|
54
|
+
.describe('Absolute path to the build output directory to deploy (e.g. "/Users/me/project/dist")'),
|
|
55
|
+
labels: z
|
|
56
|
+
.array(z.string())
|
|
57
|
+
.optional()
|
|
58
|
+
.describe(`Labels for organizing deployments (e.g. ["production", "v1.2"]). Lowercase, ${LABEL_CONSTRAINTS.MIN_LENGTH}-${LABEL_CONSTRAINTS.MAX_LENGTH} chars, allows . _ - separators.`),
|
|
59
|
+
password: z
|
|
60
|
+
.string()
|
|
61
|
+
.optional()
|
|
62
|
+
.describe(`Optional password to gate the deployment behind an unlock prompt (${PASSWORD_CONSTRAINTS.MIN_LENGTH}–${PASSWORD_CONSTRAINTS.MAX_LENGTH} characters; whitespace significant). Visitors must enter this password before viewing the site, including on any custom domains pointing at it.`),
|
|
40
63
|
},
|
|
41
64
|
}, ({ path, labels, password }) => call(() => ship.deployments.upload(path, { labels, password, via: 'mcp' })));
|
|
42
65
|
server.registerTool('deployments_list', {
|
|
@@ -47,22 +70,30 @@ export function createServer(ship) {
|
|
|
47
70
|
description: 'Get deployment details including URL, status, file count, size, labels, and password protection state.',
|
|
48
71
|
annotations: READ,
|
|
49
72
|
inputSchema: {
|
|
50
|
-
deployment: z
|
|
73
|
+
deployment: z
|
|
74
|
+
.string()
|
|
75
|
+
.describe('Deployment hostname (e.g. "happy-cat-abc1234.shipstatic.com"). Returned by deployments_upload or deployments_list.'),
|
|
51
76
|
},
|
|
52
77
|
}, ({ deployment }) => call(() => ship.deployments.get(deployment)));
|
|
53
78
|
server.registerTool('deployments_set', {
|
|
54
79
|
description: 'Update deployment labels. Replaces all existing labels.',
|
|
55
80
|
annotations: WRITE,
|
|
56
81
|
inputSchema: {
|
|
57
|
-
deployment: z
|
|
58
|
-
|
|
82
|
+
deployment: z
|
|
83
|
+
.string()
|
|
84
|
+
.describe('Deployment hostname (e.g. "happy-cat-abc1234.shipstatic.com"). Use deployments_list to find deployments.'),
|
|
85
|
+
labels: z
|
|
86
|
+
.array(z.string())
|
|
87
|
+
.describe('Labels to set. Replaces all existing labels. Pass empty array to clear.'),
|
|
59
88
|
},
|
|
60
89
|
}, ({ deployment, labels }) => call(() => ship.deployments.set(deployment, { labels })));
|
|
61
90
|
server.registerTool('deployments_remove', {
|
|
62
91
|
description: 'Permanently delete a deployment and its files. You MUST confirm with the user before calling this tool, referencing the deployment.',
|
|
63
92
|
annotations: DESTRUCTIVE,
|
|
64
93
|
inputSchema: {
|
|
65
|
-
deployment: z
|
|
94
|
+
deployment: z
|
|
95
|
+
.string()
|
|
96
|
+
.describe('Deployment hostname to delete (e.g. "happy-cat-abc1234.shipstatic.com")'),
|
|
66
97
|
},
|
|
67
98
|
}, ({ deployment }) => call(() => ship.deployments.remove(deployment)));
|
|
68
99
|
// Domains
|
|
@@ -71,8 +102,14 @@ export function createServer(ship) {
|
|
|
71
102
|
annotations: WRITE,
|
|
72
103
|
inputSchema: {
|
|
73
104
|
domain: z.string().describe('Domain name (e.g. "www.example.com" or "blog.example.com")'),
|
|
74
|
-
deployment: z
|
|
75
|
-
|
|
105
|
+
deployment: z
|
|
106
|
+
.string()
|
|
107
|
+
.optional()
|
|
108
|
+
.describe('Deployment to serve on this domain (e.g. "happy-cat-abc1234.shipstatic.com"). Omit to reserve the domain without linking.'),
|
|
109
|
+
labels: z
|
|
110
|
+
.array(z.string())
|
|
111
|
+
.optional()
|
|
112
|
+
.describe('Labels for organizing domains (e.g. ["production"]).'),
|
|
76
113
|
},
|
|
77
114
|
}, ({ domain, deployment, labels }) => call(() => ship.domains.set(domain, { deployment, labels })));
|
|
78
115
|
server.registerTool('domains_list', {
|
|
@@ -83,42 +120,54 @@ export function createServer(ship) {
|
|
|
83
120
|
description: 'Get domain details including URL, linked deployment, verification status, and labels.',
|
|
84
121
|
annotations: READ,
|
|
85
122
|
inputSchema: {
|
|
86
|
-
domain: z
|
|
123
|
+
domain: z
|
|
124
|
+
.string()
|
|
125
|
+
.describe('Domain name (e.g. "www.example.com"). Use domains_list to find names.'),
|
|
87
126
|
},
|
|
88
127
|
}, ({ domain }) => call(() => ship.domains.get(domain)));
|
|
89
128
|
server.registerTool('domains_records', {
|
|
90
129
|
description: 'Get the DNS records the user needs to configure at their DNS provider. Call after domains_set. You MUST show the returned records to the user.',
|
|
91
130
|
annotations: READ,
|
|
92
131
|
inputSchema: {
|
|
93
|
-
domain: z
|
|
132
|
+
domain: z
|
|
133
|
+
.string()
|
|
134
|
+
.describe('Domain name. Must be a domain previously created with domains_set.'),
|
|
94
135
|
},
|
|
95
136
|
}, ({ domain }) => call(() => ship.domains.records(domain)));
|
|
96
137
|
server.registerTool('domains_dns', {
|
|
97
138
|
description: 'Look up the DNS provider for a domain (e.g. Cloudflare, Namecheap). Helps the user know where to configure their DNS records.',
|
|
98
139
|
annotations: READ,
|
|
99
140
|
inputSchema: {
|
|
100
|
-
domain: z
|
|
141
|
+
domain: z
|
|
142
|
+
.string()
|
|
143
|
+
.describe('Domain name to look up DNS provider for (e.g. "www.example.com")'),
|
|
101
144
|
},
|
|
102
145
|
}, ({ domain }) => call(() => ship.domains.dns(domain)));
|
|
103
146
|
server.registerTool('domains_share', {
|
|
104
147
|
description: 'Get a shareable DNS setup hash for a domain. The hash can be shared with the user so they can view the required DNS records without needing an API key.',
|
|
105
148
|
annotations: READ,
|
|
106
149
|
inputSchema: {
|
|
107
|
-
domain: z
|
|
150
|
+
domain: z
|
|
151
|
+
.string()
|
|
152
|
+
.describe('Domain name to generate a share link for. Must be a domain previously created with domains_set.'),
|
|
108
153
|
},
|
|
109
154
|
}, ({ domain }) => call(() => ship.domains.share(domain)));
|
|
110
155
|
server.registerTool('domains_validate', {
|
|
111
156
|
description: 'Check if a domain name is valid and available before creating it. Returns the normalized form and availability.',
|
|
112
157
|
annotations: READ,
|
|
113
158
|
inputSchema: {
|
|
114
|
-
domain: z
|
|
159
|
+
domain: z
|
|
160
|
+
.string()
|
|
161
|
+
.describe('Domain name to check (e.g. "www.example.com"). Call before domains_set to check availability.'),
|
|
115
162
|
},
|
|
116
163
|
}, ({ domain }) => call(() => ship.domains.validate(domain)));
|
|
117
164
|
server.registerTool('domains_verify', {
|
|
118
165
|
description: 'Trigger DNS verification for a custom domain. Call after the user has configured DNS records from domains_records. Verification is asynchronous — the domain status updates once DNS propagates.',
|
|
119
166
|
annotations: WRITE,
|
|
120
167
|
inputSchema: {
|
|
121
|
-
domain: z
|
|
168
|
+
domain: z
|
|
169
|
+
.string()
|
|
170
|
+
.describe('Domain name to verify DNS for. Must be a domain previously created with domains_set.'),
|
|
122
171
|
},
|
|
123
172
|
}, ({ domain }) => call(() => ship.domains.verify(domain)));
|
|
124
173
|
server.registerTool('domains_remove', {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipstatic/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0-beta.0",
|
|
4
4
|
"mcpName": "com.shipstatic/mcp",
|
|
5
5
|
"description": "ShipStatic MCP — deploy static websites from AI agents. Full toolset incl. custom domains. Free hosted endpoint at mcp.shipstatic.com — no install.",
|
|
6
6
|
"type": "module",
|
|
@@ -45,20 +45,24 @@
|
|
|
45
45
|
"license": "MIT",
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
48
|
-
"@shipstatic/ship": "
|
|
48
|
+
"@shipstatic/ship": "2.0.0-beta.2",
|
|
49
49
|
"zod": "^4.3.6"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@
|
|
52
|
+
"@biomejs/biome": "2.5.5",
|
|
53
|
+
"@shipstatic/types": "2.5.0-beta.0",
|
|
53
54
|
"@types/node": "^25.6.0",
|
|
54
|
-
"
|
|
55
|
+
"@vitest/coverage-v8": "4.1.10",
|
|
55
56
|
"typescript": "^6.0.2",
|
|
56
|
-
"vitest": "
|
|
57
|
+
"vitest": "4.1.10"
|
|
57
58
|
},
|
|
58
59
|
"scripts": {
|
|
59
60
|
"build": "tsc",
|
|
60
61
|
"clean": "rm -rf dist",
|
|
61
62
|
"test": "vitest",
|
|
62
|
-
"typecheck": "tsc
|
|
63
|
+
"typecheck": "tsc -p tsconfig.check.json --noEmit",
|
|
64
|
+
"lint": "biome check .",
|
|
65
|
+
"format": "biome format --write .",
|
|
66
|
+
"coverage": "vitest --run --coverage"
|
|
63
67
|
}
|
|
64
68
|
}
|