@shipstatic/mcp 1.0.0-beta.4 → 1.0.0-beta.6
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 +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.js +11 -2
- package/dist/server.js +8 -11
- package/dist/tools.d.ts +20 -0
- package/dist/tools.js +35 -0
- package/dist/vocabulary.d.ts +69 -11
- package/dist/vocabulary.js +70 -12
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ 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_TOKEN` is optional. Without it, deploys behave like the hosted endpoint (public, claim URL, 3
|
|
88
|
+
`SHIP_TOKEN` is optional. Without it, deploys behave like the hosted endpoint (public, claim URL, expire in 3 days). With it, you get permanent deployments, the full toolset, and bigger limits.
|
|
89
89
|
|
|
90
90
|
Get a free API key at [my.shipstatic.com/api-key](https://my.shipstatic.com/api-key):
|
|
91
91
|
|
package/dist/index.d.ts
CHANGED
|
@@ -16,10 +16,19 @@
|
|
|
16
16
|
* offer a Worker. Exporting them would make this package's API "stdio's
|
|
17
17
|
* internals, plus some shared bits" instead of a contract.
|
|
18
18
|
*
|
|
19
|
+
* Every name below answers a question the second transport must otherwise
|
|
20
|
+
* answer for itself, and each admission was a restatement deleted, not a
|
|
21
|
+
* convenience added: `SERVER_NAME` and `UPLOAD_TOOL_NAME` were literals in two
|
|
22
|
+
* repos (the first also correlates the Apps-SDK widget to the connector),
|
|
23
|
+
* `PUBLIC_EXPIRY` was the same duration written out eight times,
|
|
24
|
+
* `DESCRIPTION_BLOCKS` the fragments two tool descriptions genuinely share,
|
|
25
|
+
* and `ACCOUNT_TOOL_NAMES` is what lets the hosted catalogue fence name the
|
|
26
|
+
* fourteen without counting them again.
|
|
27
|
+
*
|
|
19
28
|
* `tests/index.test.ts` fences both directions — nothing missing, nothing
|
|
20
29
|
* extra — because adding an export is the quiet failure: everything published
|
|
21
30
|
* becomes a breaking change to remove.
|
|
22
31
|
*/
|
|
23
32
|
export { type CallFn, type CallOptions, createCall, type ErrorHints } from './call.js';
|
|
24
|
-
export { registerAccountTools } from './tools.js';
|
|
25
|
-
export { ANNOTATIONS, INSTRUCTION_BLOCKS, PARAM_DESCRIPTIONS } from './vocabulary.js';
|
|
33
|
+
export { ACCOUNT_TOOL_NAMES, registerAccountTools } from './tools.js';
|
|
34
|
+
export { ANNOTATIONS, DESCRIPTION_BLOCKS, INSTRUCTION_BLOCKS, PARAM_DESCRIPTIONS, PUBLIC_EXPIRY, SERVER_NAME, UPLOAD_TOOL_NAME, } from './vocabulary.js';
|
package/dist/index.js
CHANGED
|
@@ -16,10 +16,19 @@
|
|
|
16
16
|
* offer a Worker. Exporting them would make this package's API "stdio's
|
|
17
17
|
* internals, plus some shared bits" instead of a contract.
|
|
18
18
|
*
|
|
19
|
+
* Every name below answers a question the second transport must otherwise
|
|
20
|
+
* answer for itself, and each admission was a restatement deleted, not a
|
|
21
|
+
* convenience added: `SERVER_NAME` and `UPLOAD_TOOL_NAME` were literals in two
|
|
22
|
+
* repos (the first also correlates the Apps-SDK widget to the connector),
|
|
23
|
+
* `PUBLIC_EXPIRY` was the same duration written out eight times,
|
|
24
|
+
* `DESCRIPTION_BLOCKS` the fragments two tool descriptions genuinely share,
|
|
25
|
+
* and `ACCOUNT_TOOL_NAMES` is what lets the hosted catalogue fence name the
|
|
26
|
+
* fourteen without counting them again.
|
|
27
|
+
*
|
|
19
28
|
* `tests/index.test.ts` fences both directions — nothing missing, nothing
|
|
20
29
|
* extra — because adding an export is the quiet failure: everything published
|
|
21
30
|
* becomes a breaking change to remove.
|
|
22
31
|
*/
|
|
23
32
|
export { createCall } from './call.js';
|
|
24
|
-
export { registerAccountTools } from './tools.js';
|
|
25
|
-
export { ANNOTATIONS, INSTRUCTION_BLOCKS, PARAM_DESCRIPTIONS } from './vocabulary.js';
|
|
33
|
+
export { ACCOUNT_TOOL_NAMES, registerAccountTools } from './tools.js';
|
|
34
|
+
export { ANNOTATIONS, DESCRIPTION_BLOCKS, INSTRUCTION_BLOCKS, PARAM_DESCRIPTIONS, PUBLIC_EXPIRY, SERVER_NAME, UPLOAD_TOOL_NAME, } from './vocabulary.js';
|
package/dist/server.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { IDEMPOTENCY_KEY_CONSTRAINTS } from '@shipstatic/ship';
|
|
3
2
|
import { z } from 'zod';
|
|
4
3
|
import { call } from './call.js';
|
|
5
4
|
import { registerAccountTools } from './tools.js';
|
|
6
|
-
import { ANNOTATIONS, INSTRUCTION_BLOCKS, PARAM_DESCRIPTIONS } from './vocabulary.js';
|
|
5
|
+
import { ANNOTATIONS, DESCRIPTION_BLOCKS, INSTRUCTION_BLOCKS, PARAM_DESCRIPTIONS, PUBLIC_EXPIRY, SERVER_NAME, UPLOAD_TOOL_NAME, } from './vocabulary.js';
|
|
7
6
|
// Destructured so the fifteen registrations below read as they always have.
|
|
8
7
|
// The definitions live in `vocabulary.ts` because the hosted transport speaks
|
|
9
8
|
// the same ones — that file records what is shared, what is not, and why.
|
|
10
9
|
const { CREATE } = ANNOTATIONS;
|
|
11
10
|
const B = INSTRUCTION_BLOCKS;
|
|
11
|
+
const D = DESCRIPTION_BLOCKS;
|
|
12
12
|
// Composed from the shared blocks plus the two sentences that are genuinely
|
|
13
13
|
// stdio's: how files arrive (a filesystem path) and how a caller
|
|
14
14
|
// authenticates (`SHIP_TOKEN`). The hosted transport composes the same blocks
|
|
15
15
|
// around its own two.
|
|
16
16
|
const INSTRUCTIONS = `${B.opening}
|
|
17
17
|
|
|
18
|
-
To deploy: call
|
|
18
|
+
To deploy: call ${UPLOAD_TOOL_NAME} with the build output directory path. ${B.liveAndPassword}
|
|
19
19
|
|
|
20
|
-
Without SHIP_TOKEN, deployments are public and expire in
|
|
20
|
+
Without SHIP_TOKEN, deployments are public and expire in ${PUBLIC_EXPIRY}. ${B.claim}
|
|
21
21
|
|
|
22
22
|
With SHIP_TOKEN configured, deployments go to the user's account and never expire. Listing, managing, and domain operations also require SHIP_TOKEN.
|
|
23
23
|
|
|
@@ -36,14 +36,14 @@ ${B.domainWorkflow}`;
|
|
|
36
36
|
*/
|
|
37
37
|
export function createServer(ship, version) {
|
|
38
38
|
const server = new McpServer({
|
|
39
|
-
name:
|
|
39
|
+
name: SERVER_NAME,
|
|
40
40
|
version,
|
|
41
41
|
}, {
|
|
42
42
|
instructions: INSTRUCTIONS,
|
|
43
43
|
});
|
|
44
44
|
// Deployments
|
|
45
|
-
server.registerTool(
|
|
46
|
-
description:
|
|
45
|
+
server.registerTool(UPLOAD_TOOL_NAME, {
|
|
46
|
+
description: `Deploy a static site instantly — ${D.free}. Returns the live URL, file count, and size. Without SHIP_TOKEN, the response includes a claim URL (site expires in ${PUBLIC_EXPIRY}) — always show both the deployment URL and claim URL to the user. ${D.password}`,
|
|
47
47
|
annotations: CREATE,
|
|
48
48
|
inputSchema: {
|
|
49
49
|
path: z
|
|
@@ -51,10 +51,7 @@ export function createServer(ship, version) {
|
|
|
51
51
|
.describe('Absolute path to the build output directory to deploy (e.g. "/Users/me/project/dist")'),
|
|
52
52
|
labels: z.array(z.string()).optional().describe(PARAM_DESCRIPTIONS.labels),
|
|
53
53
|
password: z.string().optional().describe(PARAM_DESCRIPTIONS.password),
|
|
54
|
-
idempotencyKey: z
|
|
55
|
-
.string()
|
|
56
|
-
.optional()
|
|
57
|
-
.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.`),
|
|
54
|
+
idempotencyKey: z.string().optional().describe(PARAM_DESCRIPTIONS.idempotencyKey),
|
|
58
55
|
},
|
|
59
56
|
}, ({ path, labels, password, idempotencyKey }) => call(() => ship.deployments.upload(path, { labels, password, idempotencyKey, via: 'mcp' })));
|
|
60
57
|
// The other fourteen. Identical on every transport, so they live in the
|
package/dist/tools.d.ts
CHANGED
|
@@ -30,4 +30,24 @@
|
|
|
30
30
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
31
31
|
import type Ship from '@shipstatic/ship';
|
|
32
32
|
import type { CallFn } from './call.js';
|
|
33
|
+
/**
|
|
34
|
+
* The fourteen, by name, in registration order.
|
|
35
|
+
*
|
|
36
|
+
* Exported so a second transport can state its expected catalogue as
|
|
37
|
+
* `[UPLOAD_TOOL_NAME, ...ACCOUNT_TOOL_NAMES]` instead of listing fifteen
|
|
38
|
+
* strings it would then have to keep in agreement with this file — the hosted
|
|
39
|
+
* parity fence is the consumer, and "fifteen" is otherwise a number two repos
|
|
40
|
+
* count separately.
|
|
41
|
+
*
|
|
42
|
+
* **Deliberately a list beside the registrations rather than a table they are
|
|
43
|
+
* generated from.** A `Record<name, factory>` would make the pairing
|
|
44
|
+
* structural, and it would cost the zod→handler inference every one of the
|
|
45
|
+
* fourteen one-liners below relies on: `({ deployment }) => …` is typed today
|
|
46
|
+
* from the `inputSchema` literal in the same call, and a loop over a
|
|
47
|
+
* heterogeneous table cannot correlate the two. The same guarantee costs
|
|
48
|
+
* nothing as a set comparison, and `tests/server.test.ts` makes it — through a
|
|
49
|
+
* real `tools/list`, so a registration without a name, a name without a
|
|
50
|
+
* registration, and a typo in either all turn it red.
|
|
51
|
+
*/
|
|
52
|
+
export declare const ACCOUNT_TOOL_NAMES: readonly ["deployments_list", "deployments_get", "deployments_set", "deployments_delete", "domains_set", "domains_list", "domains_get", "domains_records", "domains_dns", "domains_share", "domains_validate", "domains_verify", "domains_delete", "whoami"];
|
|
33
53
|
export declare function registerAccountTools(server: McpServer, ship: Ship, call: CallFn): void;
|
package/dist/tools.js
CHANGED
|
@@ -30,6 +30,41 @@
|
|
|
30
30
|
import { z } from 'zod';
|
|
31
31
|
import { ANNOTATIONS } from './vocabulary.js';
|
|
32
32
|
const { READ, WRITE, DESTRUCTIVE } = ANNOTATIONS;
|
|
33
|
+
/**
|
|
34
|
+
* The fourteen, by name, in registration order.
|
|
35
|
+
*
|
|
36
|
+
* Exported so a second transport can state its expected catalogue as
|
|
37
|
+
* `[UPLOAD_TOOL_NAME, ...ACCOUNT_TOOL_NAMES]` instead of listing fifteen
|
|
38
|
+
* strings it would then have to keep in agreement with this file — the hosted
|
|
39
|
+
* parity fence is the consumer, and "fifteen" is otherwise a number two repos
|
|
40
|
+
* count separately.
|
|
41
|
+
*
|
|
42
|
+
* **Deliberately a list beside the registrations rather than a table they are
|
|
43
|
+
* generated from.** A `Record<name, factory>` would make the pairing
|
|
44
|
+
* structural, and it would cost the zod→handler inference every one of the
|
|
45
|
+
* fourteen one-liners below relies on: `({ deployment }) => …` is typed today
|
|
46
|
+
* from the `inputSchema` literal in the same call, and a loop over a
|
|
47
|
+
* heterogeneous table cannot correlate the two. The same guarantee costs
|
|
48
|
+
* nothing as a set comparison, and `tests/server.test.ts` makes it — through a
|
|
49
|
+
* real `tools/list`, so a registration without a name, a name without a
|
|
50
|
+
* registration, and a typo in either all turn it red.
|
|
51
|
+
*/
|
|
52
|
+
export const ACCOUNT_TOOL_NAMES = [
|
|
53
|
+
'deployments_list',
|
|
54
|
+
'deployments_get',
|
|
55
|
+
'deployments_set',
|
|
56
|
+
'deployments_delete',
|
|
57
|
+
'domains_set',
|
|
58
|
+
'domains_list',
|
|
59
|
+
'domains_get',
|
|
60
|
+
'domains_records',
|
|
61
|
+
'domains_dns',
|
|
62
|
+
'domains_share',
|
|
63
|
+
'domains_validate',
|
|
64
|
+
'domains_verify',
|
|
65
|
+
'domains_delete',
|
|
66
|
+
'whoami',
|
|
67
|
+
];
|
|
33
68
|
/**
|
|
34
69
|
* The pagination surface, shared by every list tool because it is one
|
|
35
70
|
* contract, not two. A list answers `{<collection>, cursor}` and nothing
|
package/dist/vocabulary.d.ts
CHANGED
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
*
|
|
17
17
|
* - **The file-input schema.** A filesystem path here, inline content there:
|
|
18
18
|
* Workers has no filesystem. Structurally forced apart.
|
|
19
|
-
* - **Tool descriptions
|
|
20
|
-
* Apps-SDK caller that must be told not to base64-encode
|
|
21
|
-
* mode the filesystem path does not have.
|
|
19
|
+
* - **Tool descriptions**, as whole strings. They are deliberately rewritten
|
|
20
|
+
* hosted-side for an Apps-SDK caller that must be told not to base64-encode
|
|
21
|
+
* text — a failure mode the filesystem path does not have. What genuinely
|
|
22
|
+
* overlaps is exported as `DESCRIPTION_BLOCKS` and composed per transport.
|
|
22
23
|
* - **Anything Apps-SDK** (widget, `_meta`, `outputSchema`): hosted-only by
|
|
23
24
|
* nature.
|
|
24
25
|
*
|
|
@@ -26,6 +27,37 @@
|
|
|
26
27
|
* Everything else should be here, and adding a shared fact anywhere else is
|
|
27
28
|
* how the next year's drift starts.
|
|
28
29
|
*/
|
|
30
|
+
/**
|
|
31
|
+
* The server name every transport reports in `serverInfo`.
|
|
32
|
+
*
|
|
33
|
+
* Shared because it is not only prose: the Apps-SDK widget's bridge handshake
|
|
34
|
+
* sends `appInfo.name`, which the HOST correlates against `serverInfo.name` to
|
|
35
|
+
* tie the rendered view to the connector. Two literals kept equal by comment
|
|
36
|
+
* is exactly the shape this package exists to delete.
|
|
37
|
+
*/
|
|
38
|
+
export declare const SERVER_NAME = "shipstatic";
|
|
39
|
+
/**
|
|
40
|
+
* The one tool authored per transport — its INPUT differs (a filesystem path
|
|
41
|
+
* over stdio, inline bytes over HTTP), its NAME must not. Exported so the
|
|
42
|
+
* hosted parity fence can build the expected catalogue as
|
|
43
|
+
* `[UPLOAD_TOOL_NAME, ...ACCOUNT_TOOL_NAMES]` rather than counting to fifteen.
|
|
44
|
+
*/
|
|
45
|
+
export declare const UPLOAD_TOOL_NAME = "deployments_upload";
|
|
46
|
+
/**
|
|
47
|
+
* How long an anonymous deployment lives, in the words an agent reads.
|
|
48
|
+
*
|
|
49
|
+
* **The fact is owned by `cloudflare/api` (`DEPLOYMENT.PUBLIC_TTL`), which
|
|
50
|
+
* `@shipstatic/types` does not yet export** — so this is a restatement, and it
|
|
51
|
+
* is deliberately the ONLY one. It previously appeared in eight places across
|
|
52
|
+
* the two servers and the widget; a TTL change had to find all eight. When
|
|
53
|
+
* types exports the constant, this line becomes a derivation and nothing above
|
|
54
|
+
* it moves.
|
|
55
|
+
*
|
|
56
|
+
* A phrase rather than a number because every consumer is prose: the value has
|
|
57
|
+
* to carry its own unit, and `PUBLIC_TTL / 86400` interpolated at eight sites
|
|
58
|
+
* would restate the unit eight times instead of the number.
|
|
59
|
+
*/
|
|
60
|
+
export declare const PUBLIC_EXPIRY = "3 days";
|
|
29
61
|
/**
|
|
30
62
|
* MCP tool annotations by kind of operation. An agent reads these to decide
|
|
31
63
|
* whether it may call speculatively (`readOnlyHint`), whether a retry is free
|
|
@@ -65,14 +97,6 @@ export declare const ANNOTATIONS: {
|
|
|
65
97
|
readonly idempotentHint: true;
|
|
66
98
|
};
|
|
67
99
|
};
|
|
68
|
-
/**
|
|
69
|
-
* Deploy-parameter descriptions shared by both transports.
|
|
70
|
-
*
|
|
71
|
-
* The numbers interpolate from `@shipstatic/types` rather than being written
|
|
72
|
-
* out, so a platform constraint change reaches every agent-facing string
|
|
73
|
-
* without anyone editing prose — the same reason the API and the SDK import
|
|
74
|
-
* them instead of restating them.
|
|
75
|
-
*/
|
|
76
100
|
/**
|
|
77
101
|
* INSTRUCTIONS sentences both transports say.
|
|
78
102
|
*
|
|
@@ -95,7 +119,41 @@ export declare const INSTRUCTION_BLOCKS: {
|
|
|
95
119
|
readonly domainConcept: "- Domain: a custom domain (e.g. www.example.com) pointing to a deployment. Optional. Subdomains only — not apex domains.";
|
|
96
120
|
readonly domainWorkflow: "To add a custom domain: domains_validate → domains_set → domains_records (show DNS records to user) → user configures DNS → domains_verify.";
|
|
97
121
|
};
|
|
122
|
+
/**
|
|
123
|
+
* The fragments of the upload tool's description that both transports say.
|
|
124
|
+
*
|
|
125
|
+
* The surrounding descriptions diverge on purpose — hosted opens for an
|
|
126
|
+
* Apps-SDK caller and spends a paragraph on plain-text-vs-base64, stdio takes
|
|
127
|
+
* a filesystem path and has no such hazard — so what is shared is smaller than
|
|
128
|
+
* a sentence in one case and exactly a sentence in the other. Both were pinned
|
|
129
|
+
* by a `toContain` on each side, which meant three copies of each fragment
|
|
130
|
+
* (two sources and a test literal) held equal by nobody.
|
|
131
|
+
*
|
|
132
|
+
* Same reasoning as `INSTRUCTION_BLOCKS`, one altitude down: blocks are shared,
|
|
133
|
+
* composition is per transport.
|
|
134
|
+
*/
|
|
135
|
+
export declare const DESCRIPTION_BLOCKS: {
|
|
136
|
+
/** The no-account promise, mid-sentence in both openings. */
|
|
137
|
+
readonly free: "free, no account or API key required";
|
|
138
|
+
/** The password read-back rule — a password the user never sees locks them out. */
|
|
139
|
+
readonly password: "To make the site private, pass `password`; always show the password to the user if you set one.";
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Deploy-parameter descriptions shared by both transports.
|
|
143
|
+
*
|
|
144
|
+
* The numbers interpolate from `@shipstatic/types` rather than being written
|
|
145
|
+
* out, so a platform constraint change reaches every agent-facing string
|
|
146
|
+
* without anyone editing prose — the same reason the API and the SDK import
|
|
147
|
+
* them instead of restating them.
|
|
148
|
+
*/
|
|
98
149
|
export declare const PARAM_DESCRIPTIONS: {
|
|
99
150
|
readonly labels: "Labels for organizing deployments (e.g. [\"production\", \"v1.2\"]). Lowercase, 3-25 chars, allows . _ - separators. Up to 10.";
|
|
100
151
|
readonly password: "Optional password to gate the deployment behind an unlock prompt (6–128 characters; whitespace significant). Visitors must enter this password before viewing the site, including on any custom domains pointing at it.";
|
|
152
|
+
/**
|
|
153
|
+
* Shared even though only stdio offers the option today: the hosted door
|
|
154
|
+
* gains it with OAuth (it can scope a replay per user once callers have an
|
|
155
|
+
* identity), and the law this teaches — key the ATTEMPT, never the try — is
|
|
156
|
+
* the same one on both. The window is derived, never typed out.
|
|
157
|
+
*/
|
|
158
|
+
readonly idempotencyKey: `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 ${number} hours). Key the ATTEMPT \u2014 a run id, a commit sha, a uuid minted before the first try \u2014 never one minted fresh on each retry, which would defeat the point.`;
|
|
101
159
|
};
|
package/dist/vocabulary.js
CHANGED
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
*
|
|
17
17
|
* - **The file-input schema.** A filesystem path here, inline content there:
|
|
18
18
|
* Workers has no filesystem. Structurally forced apart.
|
|
19
|
-
* - **Tool descriptions
|
|
20
|
-
* Apps-SDK caller that must be told not to base64-encode
|
|
21
|
-
* mode the filesystem path does not have.
|
|
19
|
+
* - **Tool descriptions**, as whole strings. They are deliberately rewritten
|
|
20
|
+
* hosted-side for an Apps-SDK caller that must be told not to base64-encode
|
|
21
|
+
* text — a failure mode the filesystem path does not have. What genuinely
|
|
22
|
+
* overlaps is exported as `DESCRIPTION_BLOCKS` and composed per transport.
|
|
22
23
|
* - **Anything Apps-SDK** (widget, `_meta`, `outputSchema`): hosted-only by
|
|
23
24
|
* nature.
|
|
24
25
|
*
|
|
@@ -26,7 +27,38 @@
|
|
|
26
27
|
* Everything else should be here, and adding a shared fact anywhere else is
|
|
27
28
|
* how the next year's drift starts.
|
|
28
29
|
*/
|
|
29
|
-
import { LABEL_CONSTRAINTS, PASSWORD_CONSTRAINTS } from '@shipstatic/ship';
|
|
30
|
+
import { IDEMPOTENCY_KEY_CONSTRAINTS, LABEL_CONSTRAINTS, PASSWORD_CONSTRAINTS, } from '@shipstatic/ship';
|
|
31
|
+
/**
|
|
32
|
+
* The server name every transport reports in `serverInfo`.
|
|
33
|
+
*
|
|
34
|
+
* Shared because it is not only prose: the Apps-SDK widget's bridge handshake
|
|
35
|
+
* sends `appInfo.name`, which the HOST correlates against `serverInfo.name` to
|
|
36
|
+
* tie the rendered view to the connector. Two literals kept equal by comment
|
|
37
|
+
* is exactly the shape this package exists to delete.
|
|
38
|
+
*/
|
|
39
|
+
export const SERVER_NAME = 'shipstatic';
|
|
40
|
+
/**
|
|
41
|
+
* The one tool authored per transport — its INPUT differs (a filesystem path
|
|
42
|
+
* over stdio, inline bytes over HTTP), its NAME must not. Exported so the
|
|
43
|
+
* hosted parity fence can build the expected catalogue as
|
|
44
|
+
* `[UPLOAD_TOOL_NAME, ...ACCOUNT_TOOL_NAMES]` rather than counting to fifteen.
|
|
45
|
+
*/
|
|
46
|
+
export const UPLOAD_TOOL_NAME = 'deployments_upload';
|
|
47
|
+
/**
|
|
48
|
+
* How long an anonymous deployment lives, in the words an agent reads.
|
|
49
|
+
*
|
|
50
|
+
* **The fact is owned by `cloudflare/api` (`DEPLOYMENT.PUBLIC_TTL`), which
|
|
51
|
+
* `@shipstatic/types` does not yet export** — so this is a restatement, and it
|
|
52
|
+
* is deliberately the ONLY one. It previously appeared in eight places across
|
|
53
|
+
* the two servers and the widget; a TTL change had to find all eight. When
|
|
54
|
+
* types exports the constant, this line becomes a derivation and nothing above
|
|
55
|
+
* it moves.
|
|
56
|
+
*
|
|
57
|
+
* A phrase rather than a number because every consumer is prose: the value has
|
|
58
|
+
* to carry its own unit, and `PUBLIC_TTL / 86400` interpolated at eight sites
|
|
59
|
+
* would restate the unit eight times instead of the number.
|
|
60
|
+
*/
|
|
61
|
+
export const PUBLIC_EXPIRY = '3 days';
|
|
30
62
|
const OPEN_WORLD = { openWorldHint: true };
|
|
31
63
|
/**
|
|
32
64
|
* MCP tool annotations by kind of operation. An agent reads these to decide
|
|
@@ -48,14 +80,6 @@ export const ANNOTATIONS = {
|
|
|
48
80
|
WRITE: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, ...OPEN_WORLD },
|
|
49
81
|
DESTRUCTIVE: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, ...OPEN_WORLD },
|
|
50
82
|
};
|
|
51
|
-
/**
|
|
52
|
-
* Deploy-parameter descriptions shared by both transports.
|
|
53
|
-
*
|
|
54
|
-
* The numbers interpolate from `@shipstatic/types` rather than being written
|
|
55
|
-
* out, so a platform constraint change reaches every agent-facing string
|
|
56
|
-
* without anyone editing prose — the same reason the API and the SDK import
|
|
57
|
-
* them instead of restating them.
|
|
58
|
-
*/
|
|
59
83
|
/**
|
|
60
84
|
* INSTRUCTIONS sentences both transports say.
|
|
61
85
|
*
|
|
@@ -78,7 +102,41 @@ export const INSTRUCTION_BLOCKS = {
|
|
|
78
102
|
domainConcept: '- Domain: a custom domain (e.g. www.example.com) pointing to a deployment. Optional. Subdomains only — not apex domains.',
|
|
79
103
|
domainWorkflow: 'To add a custom domain: domains_validate → domains_set → domains_records (show DNS records to user) → user configures DNS → domains_verify.',
|
|
80
104
|
};
|
|
105
|
+
/**
|
|
106
|
+
* The fragments of the upload tool's description that both transports say.
|
|
107
|
+
*
|
|
108
|
+
* The surrounding descriptions diverge on purpose — hosted opens for an
|
|
109
|
+
* Apps-SDK caller and spends a paragraph on plain-text-vs-base64, stdio takes
|
|
110
|
+
* a filesystem path and has no such hazard — so what is shared is smaller than
|
|
111
|
+
* a sentence in one case and exactly a sentence in the other. Both were pinned
|
|
112
|
+
* by a `toContain` on each side, which meant three copies of each fragment
|
|
113
|
+
* (two sources and a test literal) held equal by nobody.
|
|
114
|
+
*
|
|
115
|
+
* Same reasoning as `INSTRUCTION_BLOCKS`, one altitude down: blocks are shared,
|
|
116
|
+
* composition is per transport.
|
|
117
|
+
*/
|
|
118
|
+
export const DESCRIPTION_BLOCKS = {
|
|
119
|
+
/** The no-account promise, mid-sentence in both openings. */
|
|
120
|
+
free: 'free, no account or API key required',
|
|
121
|
+
/** The password read-back rule — a password the user never sees locks them out. */
|
|
122
|
+
password: 'To make the site private, pass `password`; always show the password to the user if you set one.',
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Deploy-parameter descriptions shared by both transports.
|
|
126
|
+
*
|
|
127
|
+
* The numbers interpolate from `@shipstatic/types` rather than being written
|
|
128
|
+
* out, so a platform constraint change reaches every agent-facing string
|
|
129
|
+
* without anyone editing prose — the same reason the API and the SDK import
|
|
130
|
+
* them instead of restating them.
|
|
131
|
+
*/
|
|
81
132
|
export const PARAM_DESCRIPTIONS = {
|
|
82
133
|
labels: `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}.`,
|
|
83
134
|
password: `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.`,
|
|
135
|
+
/**
|
|
136
|
+
* Shared even though only stdio offers the option today: the hosted door
|
|
137
|
+
* gains it with OAuth (it can scope a replay per user once callers have an
|
|
138
|
+
* identity), and the law this teaches — key the ATTEMPT, never the try — is
|
|
139
|
+
* the same one on both. The window is derived, never typed out.
|
|
140
|
+
*/
|
|
141
|
+
idempotencyKey: `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.`,
|
|
84
142
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipstatic/mcp",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.6",
|
|
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",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"typecheck": "tsc -p tsconfig.check.json --noEmit",
|
|
25
25
|
"lint": "biome check .",
|
|
26
26
|
"format": "biome format --write .",
|
|
27
|
+
"smoke": "node smoke.mjs",
|
|
27
28
|
"coverage": "vitest --run --coverage"
|
|
28
29
|
},
|
|
29
30
|
"files": [
|