@shipstatic/mcp 0.7.0-beta.1 → 1.0.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.
Files changed (3) hide show
  1. package/README.md +14 -4
  2. package/dist/server.js +51 -11
  3. package/package.json +7 -7
package/README.md CHANGED
@@ -102,24 +102,24 @@ The hosted endpoint exposes `deployments_upload` only. The local install exposes
102
102
  | Tool | Description | Hosted |
103
103
  |------|-------------|:---:|
104
104
  | `deployments_upload` | Publish files and get a live URL instantly, optionally protected by a password | ✓ |
105
- | `deployments_list` | List all deployments with their URLs, status, labels, and password protection state | |
105
+ | `deployments_list` | List all deployments with their URLs, status, labels, and password protection state. Pages with `limit` and `cursor` | |
106
106
  | `deployments_get` | Get deployment details including URL, status, file count, size, labels, and password protection state | |
107
107
  | `deployments_set` | Update the labels on a deployment for organization and filtering | |
108
- | `deployments_remove` | Permanently remove a deployment and all its files | |
108
+ | `deployments_delete` | Permanently delete a deployment and all its files | |
109
109
 
110
110
  ### Domains
111
111
 
112
112
  | Tool | Description |
113
113
  |------|-------------|
114
114
  | `domains_set` | Connect a custom domain to your site, switch deployments, or update labels |
115
- | `domains_list` | List all domains with their linked deployment and verification status |
115
+ | `domains_list` | List all domains with their linked deployment and verification status. Pages with `limit` and `cursor` |
116
116
  | `domains_get` | Get domain details including linked deployment, verification status, and labels |
117
117
  | `domains_records` | Get the DNS records you need to configure at your DNS provider |
118
118
  | `domains_dns` | Look up which DNS provider hosts a domain (e.g. Cloudflare, Namecheap) |
119
119
  | `domains_share` | Get a shareable link so someone else can see the required DNS records |
120
120
  | `domains_validate` | Check if a domain name is valid and available before connecting it |
121
121
  | `domains_verify` | Check if DNS is configured correctly after you set up the records |
122
- | `domains_remove` | Permanently disconnect and remove a custom domain |
122
+ | `domains_delete` | Permanently disconnect and delete a custom domain |
123
123
 
124
124
  ### Account
125
125
 
@@ -127,6 +127,16 @@ The hosted endpoint exposes `deployments_upload` only. The local install exposes
127
127
  |------|-------------|
128
128
  | `whoami` | Get your account details including email, plan, and usage |
129
129
 
130
+ ### Paging
131
+
132
+ `deployments_list` and `domains_list` accept `limit` and `cursor`. Each response carries a `cursor` — pass it back to fetch the next page; `null` means you are on the last one.
133
+
134
+ ### Retrying a deploy safely
135
+
136
+ `deployments_upload` accepts an `idempotencyKey`. If a deploy times out you cannot tell "it never landed" from "it landed and the response was lost", and retrying without a key creates a second site. Send the same key on the retry and the original deployment is returned instead.
137
+
138
+ Key the *attempt*, not the try — a run id, a commit sha, or a uuid generated before the first call. A key that changes on every retry does nothing.
139
+
130
140
  ## Registry
131
141
 
132
142
  Published to the [MCP Registry](https://registry.modelcontextprotocol.io/v0.1/servers?search=com.shipstatic/mcp) as `com.shipstatic/mcp`. Registry-aware clients see both the hosted endpoint and the local install and pick the right transport for their environment.
package/dist/server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
- import { LABEL_CONSTRAINTS, PASSWORD_CONSTRAINTS } from '@shipstatic/ship';
3
+ import { IDEMPOTENCY_KEY_CONSTRAINTS, 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');
@@ -11,6 +11,15 @@ const READ = {
11
11
  idempotentHint: true,
12
12
  ...OPEN_WORLD,
13
13
  };
14
+ /**
15
+ * Deploys carry no `idempotentHint`, and that stays true now that
16
+ * `idempotencyKey` exists. The annotation is a STATIC per-tool claim; the
17
+ * property it would assert is per-CALL — true only when the caller supplies a
18
+ * key, false for the keyless caller, who is the common one. Advertising it
19
+ * would tell every agent that any retry is free, which is exactly wrong for
20
+ * the majority. An annotation an agent trusts wrongly is worse than one it
21
+ * never reads.
22
+ */
14
23
  const CREATE = { readOnlyHint: false, destructiveHint: false, ...OPEN_WORLD };
15
24
  const WRITE = {
16
25
  readOnlyHint: false,
@@ -24,6 +33,31 @@ const DESTRUCTIVE = {
24
33
  idempotentHint: true,
25
34
  ...OPEN_WORLD,
26
35
  };
36
+ /**
37
+ * The pagination surface, shared by every list tool because it is one
38
+ * contract, not two. A list answers `{<collection>, cursor}` and nothing
39
+ * else — `cursor` carries the whole has-more signal and is null on the last
40
+ * page, so there is no `total` to ask for and no has-more boolean.
41
+ *
42
+ * No upper bound is stated here on purpose. The API clamps an unusable
43
+ * `limit` server-side and owns that number; restating a cap in the tool
44
+ * schema would give one fact two owners and let them drift. `min(1)` is not
45
+ * a cap — it rejects a value that could never mean anything.
46
+ */
47
+ const PAGINATION_INPUT = {
48
+ limit: z
49
+ .number()
50
+ .int()
51
+ .min(1)
52
+ .optional()
53
+ .describe('Maximum number of items to return in one page. Omit for the server default.'),
54
+ cursor: z
55
+ .string()
56
+ .optional()
57
+ .describe("Opaque position from the previous response's `cursor` field; omit for the first page."),
58
+ };
59
+ /** Appended to every list tool's description — the paging contract, stated once. */
60
+ const PAGING_NOTE = " The response's `cursor` is null on the last page; pass it back as `cursor` to fetch the next.";
27
61
  const INSTRUCTIONS = `ShipStatic deploys static websites instantly. Free, no account required.
28
62
 
29
63
  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.
@@ -55,17 +89,22 @@ export function createServer(ship) {
55
89
  labels: z
56
90
  .array(z.string())
57
91
  .optional()
58
- .describe(`Labels for organizing deployments (e.g. ["production", "v1.2"]). Lowercase, ${LABEL_CONSTRAINTS.MIN_LENGTH}-${LABEL_CONSTRAINTS.MAX_LENGTH} chars, allows . _ - separators.`),
92
+ .describe(`Labels for organizing deployments (e.g. ["production", "v1.2"]). Lowercase, ${LABEL_CONSTRAINTS.MIN_LENGTH}-${LABEL_CONSTRAINTS.MAX_LENGTH} chars, allows . _ - separators. Up to ${LABEL_CONSTRAINTS.MAX_COUNT}.`),
59
93
  password: z
60
94
  .string()
61
95
  .optional()
62
96
  .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.`),
97
+ idempotencyKey: z
98
+ .string()
99
+ .optional()
100
+ .describe(`Makes this deploy replayable instead of repeatable. A deploy is not naturally idempotent: if a call times out you cannot tell "it never landed" from "it landed and the response was lost", and retrying creates a second deployment. Send the same key on the retry and the original deployment is replayed instead (within ${IDEMPOTENCY_KEY_CONSTRAINTS.WINDOW_SECONDS / 3600} hours). Key the ATTEMPT — a run id, a commit sha, a uuid minted before the first try — never one minted fresh on each retry, which would defeat the point.`),
63
101
  },
64
- }, ({ path, labels, password }) => call(() => ship.deployments.upload(path, { labels, password, via: 'mcp' })));
102
+ }, ({ path, labels, password, idempotencyKey }) => call(() => ship.deployments.upload(path, { labels, password, idempotencyKey, via: 'mcp' })));
65
103
  server.registerTool('deployments_list', {
66
- description: 'List all deployments with their URLs, status, labels, and password protection state.',
104
+ description: `List all deployments with their URLs, status, labels, and password protection state.${PAGING_NOTE}`,
67
105
  annotations: READ,
68
- }, () => call(() => ship.deployments.list()));
106
+ inputSchema: PAGINATION_INPUT,
107
+ }, ({ limit, cursor }) => call(() => ship.deployments.list({ limit, cursor })));
69
108
  server.registerTool('deployments_get', {
70
109
  description: 'Get deployment details including URL, status, file count, size, labels, and password protection state.',
71
110
  annotations: READ,
@@ -87,7 +126,7 @@ export function createServer(ship) {
87
126
  .describe('Labels to set. Replaces all existing labels. Pass empty array to clear.'),
88
127
  },
89
128
  }, ({ deployment, labels }) => call(() => ship.deployments.set(deployment, { labels })));
90
- server.registerTool('deployments_remove', {
129
+ server.registerTool('deployments_delete', {
91
130
  description: 'Permanently delete a deployment and its files. You MUST confirm with the user before calling this tool, referencing the deployment.',
92
131
  annotations: DESTRUCTIVE,
93
132
  inputSchema: {
@@ -95,7 +134,7 @@ export function createServer(ship) {
95
134
  .string()
96
135
  .describe('Deployment hostname to delete (e.g. "happy-cat-abc1234.shipstatic.com")'),
97
136
  },
98
- }, ({ deployment }) => call(() => ship.deployments.remove(deployment)));
137
+ }, ({ deployment }) => call(() => ship.deployments.delete(deployment)));
99
138
  // Domains
100
139
  server.registerTool('domains_set', {
101
140
  description: 'Create or update a custom domain. Can reserve a name (omit deployment), link it to a deployment, switch deployments, or update labels. After creating, call domains_records and show the DNS records to the user.',
@@ -113,9 +152,10 @@ export function createServer(ship) {
113
152
  },
114
153
  }, ({ domain, deployment, labels }) => call(() => ship.domains.set(domain, { deployment, labels })));
115
154
  server.registerTool('domains_list', {
116
- description: 'List all domains with their URLs, linked deployment, and verification status.',
155
+ description: `List all domains with their URLs, linked deployment, and verification status.${PAGING_NOTE}`,
117
156
  annotations: READ,
118
- }, () => call(() => ship.domains.list()));
157
+ inputSchema: PAGINATION_INPUT,
158
+ }, ({ limit, cursor }) => call(() => ship.domains.list({ limit, cursor })));
119
159
  server.registerTool('domains_get', {
120
160
  description: 'Get domain details including URL, linked deployment, verification status, and labels.',
121
161
  annotations: READ,
@@ -170,13 +210,13 @@ export function createServer(ship) {
170
210
  .describe('Domain name to verify DNS for. Must be a domain previously created with domains_set.'),
171
211
  },
172
212
  }, ({ domain }) => call(() => ship.domains.verify(domain)));
173
- server.registerTool('domains_remove', {
213
+ server.registerTool('domains_delete', {
174
214
  description: 'Permanently delete a domain. You MUST confirm with the user before calling this tool, referencing the domain name.',
175
215
  annotations: DESTRUCTIVE,
176
216
  inputSchema: {
177
217
  domain: z.string().describe('Domain name to delete (e.g. "www.example.com")'),
178
218
  },
179
- }, ({ domain }) => call(() => ship.domains.remove(domain)));
219
+ }, ({ domain }) => call(() => ship.domains.delete(domain)));
180
220
  // Debugging
181
221
  server.registerTool('whoami', {
182
222
  description: 'Show authenticated account details including email, plan, and usage.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipstatic/mcp",
3
- "version": "0.7.0-beta.1",
3
+ "version": "1.0.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",
@@ -54,16 +54,16 @@
54
54
  "author": "ShipStatic",
55
55
  "license": "MIT",
56
56
  "dependencies": {
57
- "@modelcontextprotocol/sdk": "^1.29.0",
58
- "@shipstatic/ship": "2.0.0-beta.2",
59
- "zod": "^4.3.6"
57
+ "@modelcontextprotocol/sdk": "^1.30.0",
58
+ "@shipstatic/ship": "2.0.0-beta.15",
59
+ "zod": "^4.4.3"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@biomejs/biome": "2.5.5",
63
- "@shipstatic/types": "2.5.0-beta.0",
64
- "@types/node": "^25.6.0",
63
+ "@shipstatic/types": "2.5.0-beta.18",
64
+ "@types/node": "^25.9.5",
65
65
  "@vitest/coverage-v8": "4.1.10",
66
- "typescript": "^6.0.2",
66
+ "typescript": "^6.0.3",
67
67
  "vitest": "4.1.10"
68
68
  },
69
69
  "packageManager": "pnpm@10.12.4"