@shipstatic/mcp 1.11.0 → 1.12.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/dist/call.d.ts +0 -13
- package/dist/call.js +10 -12
- package/dist/server.js +2 -0
- package/dist/tools.d.ts +37 -0
- package/dist/tools.js +39 -9
- package/dist/vocabulary.d.ts +2 -3
- package/dist/vocabulary.js +2 -3
- package/package.json +2 -2
package/dist/call.d.ts
CHANGED
|
@@ -13,19 +13,6 @@ export interface ErrorHints {
|
|
|
13
13
|
export type CallFn = <T>(fn: () => Promise<T>) => Promise<CallToolResult>;
|
|
14
14
|
export interface CallOptions {
|
|
15
15
|
hints: ErrorHints;
|
|
16
|
-
/**
|
|
17
|
-
* Attach a plain-object SUCCESS result as `structuredContent` beside the
|
|
18
|
-
* text. Hosted-only: it is what feeds the Apps-SDK widget, and the MCP spec
|
|
19
|
-
* pairs it with an `outputSchema`, which is a hand-maintained zod twin of a
|
|
20
|
-
* published type. One such twin is worth it for a widget; fifteen would be a
|
|
21
|
-
* drift surface with no consumer asking for it. See
|
|
22
|
-
* `cloudflare/mcp/CLAUDE.md`, "What deliberately differs".
|
|
23
|
-
*
|
|
24
|
-
* It does NOT gate the ERROR envelope, which every transport carries — see
|
|
25
|
-
* `toErrorResult`. The objection above is about fifteen success shapes; a
|
|
26
|
-
* failure has exactly one published shape, and no schema to keep in step.
|
|
27
|
-
*/
|
|
28
|
-
structuredContent?: boolean;
|
|
29
16
|
/**
|
|
30
17
|
* Called when the API refuses on CREDENTIAL grounds — and only then.
|
|
31
18
|
*
|
package/dist/call.js
CHANGED
|
@@ -39,7 +39,7 @@ const MAINTENANCE_HINT = 'The platform is temporarily closed for scheduled maint
|
|
|
39
39
|
* kept equal by review.
|
|
40
40
|
*/
|
|
41
41
|
export function createCall(options) {
|
|
42
|
-
const { hints,
|
|
42
|
+
const { hints, onAuthFailure } = options;
|
|
43
43
|
return async function call(fn) {
|
|
44
44
|
try {
|
|
45
45
|
const result = await fn();
|
|
@@ -48,13 +48,15 @@ export function createCall(options) {
|
|
|
48
48
|
if (result === undefined) {
|
|
49
49
|
return { content: [{ type: 'text', text: 'Done.' }] };
|
|
50
50
|
}
|
|
51
|
+
// The wire shape rides twice: as the text every client reads, and as
|
|
52
|
+
// `structuredContent` for the tool's `outputSchema` (every tool declares
|
|
53
|
+
// one, imported from the constitution; the SDK validates the result
|
|
54
|
+
// against it and refuses a result that carries none). A non-object has
|
|
55
|
+
// no schema to satisfy and rides the text channel alone.
|
|
51
56
|
const text = JSON.stringify(result, null, 2);
|
|
52
|
-
const structured = structuredContent && isPlainObject(result)
|
|
53
|
-
? result
|
|
54
|
-
: undefined;
|
|
55
57
|
return {
|
|
56
58
|
content: [{ type: 'text', text }],
|
|
57
|
-
...(
|
|
59
|
+
...(isPlainObject(result) ? { structuredContent: result } : {}),
|
|
58
60
|
};
|
|
59
61
|
}
|
|
60
62
|
catch (error) {
|
|
@@ -98,13 +100,9 @@ function isPlainObject(value) {
|
|
|
98
100
|
* need of a precise backoff to parse "try again in 9 minutes" out of English.
|
|
99
101
|
*
|
|
100
102
|
* Safe on every arm, and checked rather than assumed: the MCP SDK validates
|
|
101
|
-
* `structuredContent`
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* It is deliberately NOT behind `CallOptions.structuredContent` — that flag
|
|
106
|
-
* governs success shapes, where the schema-twin objection lives. A failure has
|
|
107
|
-
* one published shape on every transport.
|
|
103
|
+
* `structuredContent` against a declared `outputSchema` only when `isError`
|
|
104
|
+
* is not set, so an error envelope never meets a success schema. Every tool
|
|
105
|
+
* declares one (imported from the constitution), and this arm is unaffected.
|
|
108
106
|
*/
|
|
109
107
|
function toErrorResult(error, hints, onAuthFailure) {
|
|
110
108
|
if (isShipError(error)) {
|
package/dist/server.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { DeploymentVia, MY_API_KEY_URL } from '@shipstatic/types';
|
|
3
|
+
import { DeploymentCreateResponseSchema } from '@shipstatic/types/schemas';
|
|
3
4
|
import { z } from 'zod';
|
|
4
5
|
import { call } from './call.js';
|
|
5
6
|
import { registerAccountTools, TOOLS } from './tools.js';
|
|
@@ -43,6 +44,7 @@ export function createServer(ship, options) {
|
|
|
43
44
|
// shares with the hosted one; only the input schema is this door's own.
|
|
44
45
|
description: `Deploy a static site instantly: ${D.free}. Returns the live URL, file count, and size. Without SHIP_TOKEN, the response also includes a one-time claim URL, and the site expires in ${PUBLIC_EXPIRY} unless claimed. ${D.password}`,
|
|
45
46
|
annotations: annotate(TOOLS[UPLOAD_TOOL_NAME]),
|
|
47
|
+
outputSchema: DeploymentCreateResponseSchema,
|
|
46
48
|
inputSchema: {
|
|
47
49
|
path: z
|
|
48
50
|
.string()
|
package/dist/tools.d.ts
CHANGED
|
@@ -35,9 +35,18 @@
|
|
|
35
35
|
* the agent, the title reads as English for the human, and the description
|
|
36
36
|
* carries every precision neither can. Both catalogue pins assert a title on
|
|
37
37
|
* every tool, so the next one cannot be added without one.
|
|
38
|
+
*
|
|
39
|
+
* **Every tool publishes an `outputSchema`, imported from the constitution**
|
|
40
|
+
* (`@shipstatic/types/schemas`), never written here: a schema written beside
|
|
41
|
+
* a tool is a twin of a published type, and the SDK enforces it on every
|
|
42
|
+
* success, so a twin that drifts fails a call that succeeded. The constitution
|
|
43
|
+
* fences its schemas to its interfaces at compile time; this file only names
|
|
44
|
+
* which one each tool answers with, and `call` attaches the result as
|
|
45
|
+
* `structuredContent` so the schema has something to validate.
|
|
38
46
|
*/
|
|
39
47
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
40
48
|
import type Ship from '@shipstatic/ship';
|
|
49
|
+
import { z } from 'zod';
|
|
41
50
|
import type { CallFn } from './call.js';
|
|
42
51
|
/**
|
|
43
52
|
* THE REGISTRY: one row per tool, every fact about it that a host reads.
|
|
@@ -154,4 +163,32 @@ export type AccountToolName = {
|
|
|
154
163
|
* place a tool's account requirement is stated.
|
|
155
164
|
*/
|
|
156
165
|
export declare const ACCOUNT_TOOL_NAMES: AccountToolName[];
|
|
166
|
+
/**
|
|
167
|
+
* What `whoami` answers: exactly the keys its description names, and the
|
|
168
|
+
* schema IS the projection.
|
|
169
|
+
*
|
|
170
|
+
* `Account` also carries billing state, the API-key hint, the picture and
|
|
171
|
+
* timestamps, none of which the description mentions and none of which an
|
|
172
|
+
* agent acts on. A tool's result is the shape its description states, so the
|
|
173
|
+
* result is the constitution's `AccountSchema` narrowed to five keys, and
|
|
174
|
+
* parsing the wire through it is what drops the rest: one declaration owns
|
|
175
|
+
* both the published `outputSchema` and the projection. `suspended` is
|
|
176
|
+
* deliberately out: it means every write is refused, and the refusal says so
|
|
177
|
+
* itself at the moment it matters.
|
|
178
|
+
*/
|
|
179
|
+
export declare const ACCOUNT_SUMMARY: z.ZodObject<{
|
|
180
|
+
name: z.ZodNullable<z.ZodString>;
|
|
181
|
+
email: z.ZodString;
|
|
182
|
+
plan: z.ZodString;
|
|
183
|
+
usage: z.ZodObject<{
|
|
184
|
+
deployments: z.ZodInt;
|
|
185
|
+
platformDomains: z.ZodInt;
|
|
186
|
+
customDomains: z.ZodInt;
|
|
187
|
+
}, z.core.$strip>;
|
|
188
|
+
caps: z.ZodObject<{
|
|
189
|
+
deployments: z.ZodInt;
|
|
190
|
+
platformDomains: z.ZodInt;
|
|
191
|
+
customDomains: z.ZodInt;
|
|
192
|
+
}, z.core.$strip>;
|
|
193
|
+
}, z.core.$strip>;
|
|
157
194
|
export declare function registerAccountTools(server: McpServer, ship: Ship, call: CallFn): void;
|
package/dist/tools.js
CHANGED
|
@@ -35,7 +35,16 @@
|
|
|
35
35
|
* the agent, the title reads as English for the human, and the description
|
|
36
36
|
* carries every precision neither can. Both catalogue pins assert a title on
|
|
37
37
|
* every tool, so the next one cannot be added without one.
|
|
38
|
+
*
|
|
39
|
+
* **Every tool publishes an `outputSchema`, imported from the constitution**
|
|
40
|
+
* (`@shipstatic/types/schemas`), never written here: a schema written beside
|
|
41
|
+
* a tool is a twin of a published type, and the SDK enforces it on every
|
|
42
|
+
* success, so a twin that drifts fails a call that succeeded. The constitution
|
|
43
|
+
* fences its schemas to its interfaces at compile time; this file only names
|
|
44
|
+
* which one each tool answers with, and `call` attaches the result as
|
|
45
|
+
* `structuredContent` so the schema has something to validate.
|
|
38
46
|
*/
|
|
47
|
+
import { AccountSchema, DeploymentDeleteResponseSchema, DeploymentListResponseSchema, DeploymentSchema, DomainDeleteResponseSchema, DomainDnsResponseSchema, DomainListResponseSchema, DomainRecordsResponseSchema, DomainSchema, DomainSetResultSchema, DomainShareResponseSchema, DomainValidateResponseSchema, DomainVerifyResponseSchema, } from '@shipstatic/types/schemas';
|
|
39
48
|
import { z } from 'zod';
|
|
40
49
|
import { annotate, titled, UPLOAD_TOOL_NAME } from './vocabulary.js';
|
|
41
50
|
/**
|
|
@@ -89,18 +98,25 @@ export const TOOLS = {
|
|
|
89
98
|
*/
|
|
90
99
|
export const ACCOUNT_TOOL_NAMES = Object.keys(TOOLS).filter((name) => TOOLS[name].auth === 'required');
|
|
91
100
|
/**
|
|
92
|
-
* What `whoami` answers: exactly the keys its description names
|
|
101
|
+
* What `whoami` answers: exactly the keys its description names, and the
|
|
102
|
+
* schema IS the projection.
|
|
93
103
|
*
|
|
94
104
|
* `Account` also carries billing state, the API-key hint, the picture and
|
|
95
105
|
* timestamps, none of which the description mentions and none of which an
|
|
96
|
-
* agent acts on. A tool's result is the shape its description states
|
|
97
|
-
* is the
|
|
98
|
-
*
|
|
99
|
-
*
|
|
106
|
+
* agent acts on. A tool's result is the shape its description states, so the
|
|
107
|
+
* result is the constitution's `AccountSchema` narrowed to five keys, and
|
|
108
|
+
* parsing the wire through it is what drops the rest: one declaration owns
|
|
109
|
+
* both the published `outputSchema` and the projection. `suspended` is
|
|
110
|
+
* deliberately out: it means every write is refused, and the refusal says so
|
|
111
|
+
* itself at the moment it matters.
|
|
100
112
|
*/
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
113
|
+
export const ACCOUNT_SUMMARY = AccountSchema.pick({
|
|
114
|
+
email: true,
|
|
115
|
+
name: true,
|
|
116
|
+
plan: true,
|
|
117
|
+
usage: true,
|
|
118
|
+
caps: true,
|
|
119
|
+
});
|
|
104
120
|
/**
|
|
105
121
|
* The pagination surface, shared by every list tool because it is one
|
|
106
122
|
* contract, not two. A list answers `{<collection>, cursor}` and nothing
|
|
@@ -134,12 +150,14 @@ export function registerAccountTools(server, ship, call) {
|
|
|
134
150
|
title: 'List Deployments',
|
|
135
151
|
description: `List all deployments with their URLs, status, labels, and password protection state.${PAGING_NOTE}`,
|
|
136
152
|
annotations: annotate(TOOLS.deployments_list),
|
|
153
|
+
outputSchema: DeploymentListResponseSchema,
|
|
137
154
|
inputSchema: PAGINATION_INPUT,
|
|
138
155
|
}), ({ limit, cursor }) => call(() => ship.deployments.list({ limit, cursor })));
|
|
139
156
|
server.registerTool('deployments_get', titled({
|
|
140
157
|
title: 'Get Deployment',
|
|
141
158
|
description: 'Get deployment details including URL, status, file count, size, labels, and password protection state.',
|
|
142
159
|
annotations: annotate(TOOLS.deployments_get),
|
|
160
|
+
outputSchema: DeploymentSchema,
|
|
143
161
|
inputSchema: {
|
|
144
162
|
deployment: z
|
|
145
163
|
.string()
|
|
@@ -150,6 +168,7 @@ export function registerAccountTools(server, ship, call) {
|
|
|
150
168
|
title: 'Update Deployment Labels',
|
|
151
169
|
description: 'Update deployment labels. Replaces all existing labels.',
|
|
152
170
|
annotations: annotate(TOOLS.deployments_set),
|
|
171
|
+
outputSchema: DeploymentSchema,
|
|
153
172
|
inputSchema: {
|
|
154
173
|
deployment: z
|
|
155
174
|
.string()
|
|
@@ -163,6 +182,7 @@ export function registerAccountTools(server, ship, call) {
|
|
|
163
182
|
title: 'Delete Deployment',
|
|
164
183
|
description: 'Permanently deletes a deployment and its files.',
|
|
165
184
|
annotations: annotate(TOOLS.deployments_delete),
|
|
185
|
+
outputSchema: DeploymentDeleteResponseSchema,
|
|
166
186
|
inputSchema: {
|
|
167
187
|
deployment: z
|
|
168
188
|
.string()
|
|
@@ -174,6 +194,7 @@ export function registerAccountTools(server, ship, call) {
|
|
|
174
194
|
title: 'Connect Custom Domain',
|
|
175
195
|
description: 'Create or update a custom domain. Can reserve a name (omit deployment), link it to a deployment, switch deployments, or update labels. domains_records then returns the DNS records to configure.',
|
|
176
196
|
annotations: annotate(TOOLS.domains_set),
|
|
197
|
+
outputSchema: DomainSetResultSchema,
|
|
177
198
|
inputSchema: {
|
|
178
199
|
domain: z.string().describe('Domain name (e.g. "www.example.com" or "blog.example.com")'),
|
|
179
200
|
deployment: z
|
|
@@ -190,12 +211,14 @@ export function registerAccountTools(server, ship, call) {
|
|
|
190
211
|
title: 'List Domains',
|
|
191
212
|
description: `List all domains with their URLs, linked deployment, and verification status.${PAGING_NOTE}`,
|
|
192
213
|
annotations: annotate(TOOLS.domains_list),
|
|
214
|
+
outputSchema: DomainListResponseSchema,
|
|
193
215
|
inputSchema: PAGINATION_INPUT,
|
|
194
216
|
}), ({ limit, cursor }) => call(() => ship.domains.list({ limit, cursor })));
|
|
195
217
|
server.registerTool('domains_get', titled({
|
|
196
218
|
title: 'Get Domain',
|
|
197
219
|
description: 'Get domain details including URL, linked deployment, verification status, and labels.',
|
|
198
220
|
annotations: annotate(TOOLS.domains_get),
|
|
221
|
+
outputSchema: DomainSchema,
|
|
199
222
|
inputSchema: {
|
|
200
223
|
domain: z
|
|
201
224
|
.string()
|
|
@@ -206,6 +229,7 @@ export function registerAccountTools(server, ship, call) {
|
|
|
206
229
|
title: 'Get DNS Records',
|
|
207
230
|
description: "Returns the DNS records to configure at the domain's DNS provider. Call after domains_set.",
|
|
208
231
|
annotations: annotate(TOOLS.domains_records),
|
|
232
|
+
outputSchema: DomainRecordsResponseSchema,
|
|
209
233
|
inputSchema: {
|
|
210
234
|
domain: z
|
|
211
235
|
.string()
|
|
@@ -216,6 +240,7 @@ export function registerAccountTools(server, ship, call) {
|
|
|
216
240
|
title: 'Get DNS Provider',
|
|
217
241
|
description: 'Returns the DNS provider recorded for the domain, if known (e.g. Cloudflare, Namecheap): where its DNS records are configured.',
|
|
218
242
|
annotations: annotate(TOOLS.domains_dns),
|
|
243
|
+
outputSchema: DomainDnsResponseSchema,
|
|
219
244
|
inputSchema: {
|
|
220
245
|
domain: z
|
|
221
246
|
.string()
|
|
@@ -226,6 +251,7 @@ export function registerAccountTools(server, ship, call) {
|
|
|
226
251
|
title: 'Share DNS Setup',
|
|
227
252
|
description: "Returns a shareable DNS setup URL that needs no API key, for whoever manages the domain's DNS.",
|
|
228
253
|
annotations: annotate(TOOLS.domains_share),
|
|
254
|
+
outputSchema: DomainShareResponseSchema,
|
|
229
255
|
inputSchema: {
|
|
230
256
|
domain: z
|
|
231
257
|
.string()
|
|
@@ -236,6 +262,7 @@ export function registerAccountTools(server, ship, call) {
|
|
|
236
262
|
title: 'Check Domain Availability',
|
|
237
263
|
description: 'Check if a domain name is valid and available before creating it. Returns the normalized form and availability.',
|
|
238
264
|
annotations: annotate(TOOLS.domains_validate),
|
|
265
|
+
outputSchema: DomainValidateResponseSchema,
|
|
239
266
|
inputSchema: {
|
|
240
267
|
domain: z
|
|
241
268
|
.string()
|
|
@@ -246,6 +273,7 @@ export function registerAccountTools(server, ship, call) {
|
|
|
246
273
|
title: 'Verify Domain DNS',
|
|
247
274
|
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.',
|
|
248
275
|
annotations: annotate(TOOLS.domains_verify),
|
|
276
|
+
outputSchema: DomainVerifyResponseSchema,
|
|
249
277
|
inputSchema: {
|
|
250
278
|
domain: z
|
|
251
279
|
.string()
|
|
@@ -256,6 +284,7 @@ export function registerAccountTools(server, ship, call) {
|
|
|
256
284
|
title: 'Delete Domain',
|
|
257
285
|
description: 'Permanently deletes a domain.',
|
|
258
286
|
annotations: annotate(TOOLS.domains_delete),
|
|
287
|
+
outputSchema: DomainDeleteResponseSchema,
|
|
259
288
|
inputSchema: {
|
|
260
289
|
domain: z.string().describe('Domain name to delete (e.g. "www.example.com")'),
|
|
261
290
|
},
|
|
@@ -265,5 +294,6 @@ export function registerAccountTools(server, ship, call) {
|
|
|
265
294
|
title: 'Show Account',
|
|
266
295
|
description: "Returns the account's email, name, plan, current usage and plan caps.",
|
|
267
296
|
annotations: annotate(TOOLS.whoami),
|
|
268
|
-
|
|
297
|
+
outputSchema: ACCOUNT_SUMMARY,
|
|
298
|
+
}), () => call(() => ship.whoami().then((account) => ACCOUNT_SUMMARY.parse(account))));
|
|
269
299
|
}
|
package/dist/vocabulary.d.ts
CHANGED
|
@@ -190,9 +190,8 @@ export declare const INSTRUCTION_BLOCKS: {
|
|
|
190
190
|
readonly opening: "ShipStatic deploys static websites instantly. Free, no account required.";
|
|
191
191
|
readonly live: "The site is live immediately.";
|
|
192
192
|
/**
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
* door, by OpenAI's data rule) composes `live` without this.
|
|
193
|
+
* A block of its own rather than a clause of `live`: two sentences, two
|
|
194
|
+
* facts, and a door may compose them independently.
|
|
196
195
|
*
|
|
197
196
|
* The read-back rule lives HERE and not in the upload description: a
|
|
198
197
|
* password the user never sees locks them out, and `instructions` is the
|
package/dist/vocabulary.js
CHANGED
|
@@ -169,9 +169,8 @@ export const INSTRUCTION_BLOCKS = {
|
|
|
169
169
|
opening: 'ShipStatic deploys static websites instantly. Free, no account required.',
|
|
170
170
|
live: 'The site is live immediately.',
|
|
171
171
|
/**
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* door, by OpenAI's data rule) composes `live` without this.
|
|
172
|
+
* A block of its own rather than a clause of `live`: two sentences, two
|
|
173
|
+
* facts, and a door may compose them independently.
|
|
175
174
|
*
|
|
176
175
|
* The read-back rule lives HERE and not in the upload description: a
|
|
177
176
|
* password the user never sees locks them out, and `instructions` is the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipstatic/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0-beta.1",
|
|
4
4
|
"mcpName": "com.shipstatic/mcp",
|
|
5
5
|
"description": "Deploy static websites, landing pages, and prototypes instantly from AI agents. Start with the hosted endpoint at mcp.shipstatic.com (no install, no signup, no API key), or install this package to deploy folders from your own machine. The same fifteen tools either way.",
|
|
6
6
|
"type": "module",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
71
71
|
"@shipstatic/ship": "2.9.2",
|
|
72
|
-
"@shipstatic/types": "2.
|
|
72
|
+
"@shipstatic/types": "2.25.0-beta.1",
|
|
73
73
|
"zod": "^4.4.3"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|