@shipstatic/mcp 0.6.0 → 0.7.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 CHANGED
@@ -85,12 +85,12 @@ Same config format — `npx @shipstatic/mcp`. Works with any MCP-compatible clie
85
85
 
86
86
  ## Free API key — permanent deployments
87
87
 
88
- `SHIP_API_KEY` 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.
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 key at [my.shipstatic.com/api-key](https://my.shipstatic.com/api-key):
90
+ Get a free API key at [my.shipstatic.com/api-key](https://my.shipstatic.com/api-key):
91
91
 
92
92
  ```bash
93
- claude mcp add shipstatic -e SHIP_API_KEY=ship-... -- npx @shipstatic/mcp
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 { isShipError, ErrorType } from '@shipstatic/ship';
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 += '\n\nHint: Set a free SHIP_API_KEY environment variable in your MCP server configuration.';
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 += '\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.';
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
- // SHIP_API_KEY is optional — without it, deployments are public (3-day expiry).
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
- const ship = new Ship({ apiKey: process.env.SHIP_API_KEY });
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
@@ -1,3 +1,3 @@
1
- import type Ship from '@shipstatic/ship';
2
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type Ship from '@shipstatic/ship';
3
3
  export declare function createServer(ship: Ship): McpServer;
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 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, ...OPEN_WORLD };
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 = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, ...OPEN_WORLD };
11
- const DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true, idempotentHint: true, ...OPEN_WORLD };
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 SHIP_API_KEY, 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.
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 SHIP_API_KEY configured, deployments go to the user's account and never expire. Listing, managing, and domain operations also require SHIP_API_KEY.
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 SHIP_API_KEY, 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.',
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.string().describe('Absolute path to the build output directory to deploy (e.g. "/Users/me/project/dist")'),
38
- labels: z.array(z.string()).optional().describe(`Labels for organizing deployments (e.g. ["production", "v1.2"]). Lowercase, ${LABEL_CONSTRAINTS.MIN_LENGTH}-${LABEL_CONSTRAINTS.MAX_LENGTH} chars, allows . _ - separators.`),
39
- password: z.string().optional().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.`),
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.string().describe('Deployment hostname (e.g. "happy-cat-abc1234.shipstatic.com"). Returned by deployments_upload or deployments_list.'),
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.string().describe('Deployment hostname (e.g. "happy-cat-abc1234.shipstatic.com"). Use deployments_list to find deployments.'),
58
- labels: z.array(z.string()).describe('Labels to set. Replaces all existing labels. Pass empty array to clear.'),
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.string().describe('Deployment hostname to delete (e.g. "happy-cat-abc1234.shipstatic.com")'),
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.string().optional().describe('Deployment to serve on this domain (e.g. "happy-cat-abc1234.shipstatic.com"). Omit to reserve the domain without linking.'),
75
- labels: z.array(z.string()).optional().describe('Labels for organizing domains (e.g. ["production"]).'),
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.string().describe('Domain name (e.g. "www.example.com"). Use domains_list to find names.'),
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.string().describe('Domain name. Must be a domain previously created with domains_set.'),
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.string().describe('Domain name to look up DNS provider for (e.g. "www.example.com")'),
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.string().describe('Domain name to generate a share link for. Must be a domain previously created with domains_set.'),
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.string().describe('Domain name to check (e.g. "www.example.com"). Call before domains_set to check availability.'),
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.string().describe('Domain name to verify DNS for. Must be a domain previously created with domains_set.'),
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.6.0",
3
+ "version": "0.7.0-beta.1",
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",
@@ -9,6 +9,16 @@
9
9
  "bin": {
10
10
  "shipstatic-mcp": "./dist/index.js"
11
11
  },
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "clean": "rm -rf dist",
15
+ "prepare": "git config core.hooksPath scripts/githooks",
16
+ "test": "vitest",
17
+ "typecheck": "tsc -p tsconfig.check.json --noEmit",
18
+ "lint": "biome check .",
19
+ "format": "biome format --write .",
20
+ "coverage": "vitest --run --coverage"
21
+ },
12
22
  "files": [
13
23
  "dist",
14
24
  "README.md"
@@ -45,20 +55,16 @@
45
55
  "license": "MIT",
46
56
  "dependencies": {
47
57
  "@modelcontextprotocol/sdk": "^1.29.0",
48
- "@shipstatic/ship": "^0.9.6",
58
+ "@shipstatic/ship": "2.0.0-beta.2",
49
59
  "zod": "^4.3.6"
50
60
  },
51
61
  "devDependencies": {
52
- "@shipstatic/types": "^0.9.8",
62
+ "@biomejs/biome": "2.5.5",
63
+ "@shipstatic/types": "2.5.0-beta.0",
53
64
  "@types/node": "^25.6.0",
54
- "husky": "^9.1.7",
65
+ "@vitest/coverage-v8": "4.1.10",
55
66
  "typescript": "^6.0.2",
56
- "vitest": "^4.1.4"
67
+ "vitest": "4.1.10"
57
68
  },
58
- "scripts": {
59
- "build": "tsc",
60
- "clean": "rm -rf dist",
61
- "test": "vitest",
62
- "typecheck": "tsc --noEmit -p tsconfig.test.json"
63
- }
64
- }
69
+ "packageManager": "pnpm@10.12.4"
70
+ }