@shipstatic/mcp 1.10.2 → 1.11.0-beta.2
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 +2 -2
- package/dist/index.d.ts +6 -4
- package/dist/index.js +6 -4
- package/dist/server.js +9 -9
- package/dist/tools.d.ts +111 -16
- package/dist/tools.js +83 -56
- package/dist/vocabulary.d.ts +65 -39
- package/dist/vocabulary.js +44 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -139,7 +139,7 @@ All fifteen tools are on both doors. What changes is not which tools exist, but
|
|
|
139
139
|
| `domains_list` | List all domains with their linked deployment and verification status. Pages with `limit` and `cursor` |
|
|
140
140
|
| `domains_get` | Get domain details including linked deployment, verification status, and labels |
|
|
141
141
|
| `domains_records` | Get the DNS records you need to configure at your DNS provider |
|
|
142
|
-
| `domains_dns` |
|
|
142
|
+
| `domains_dns` | The DNS provider recorded for a domain, if known (e.g. Cloudflare, Namecheap) |
|
|
143
143
|
| `domains_share` | Get a shareable link so someone else can see the required DNS records |
|
|
144
144
|
| `domains_validate` | Check if a domain name is valid and available before connecting it |
|
|
145
145
|
| `domains_verify` | Check if DNS is configured correctly after you set up the records |
|
|
@@ -149,7 +149,7 @@ All fifteen tools are on both doors. What changes is not which tools exist, but
|
|
|
149
149
|
|
|
150
150
|
| Tool | Description |
|
|
151
151
|
|------|-------------|
|
|
152
|
-
| `whoami` |
|
|
152
|
+
| `whoami` | Your account's email, name, plan, current usage and plan caps |
|
|
153
153
|
|
|
154
154
|
### Paging long lists
|
|
155
155
|
|
package/dist/index.d.ts
CHANGED
|
@@ -26,8 +26,10 @@
|
|
|
26
26
|
* was the same duration written out eight times, `DESCRIPTION_BLOCKS` the
|
|
27
27
|
* fragments two tool descriptions genuinely share, `ACCOUNT_TOOL_NAMES` is
|
|
28
28
|
* what lets the hosted catalogue fence name the fourteen without counting them
|
|
29
|
-
* again, and `
|
|
30
|
-
*
|
|
29
|
+
* again, `TOOLS` and `annotate` replaced four annotation classes plus a
|
|
30
|
+
* hand-annotated upload on each transport plus a separate list for the auth
|
|
31
|
+
* need (three owners for one tool's facts, 1.11.0), and `createServer`
|
|
32
|
+
* deleted three regexes in another repo's build (see below).
|
|
31
33
|
*
|
|
32
34
|
* **`createServer` is exported; the configured `call` is not.** The extension
|
|
33
35
|
* previously reached stdio's composition by REGEX-PATCHING this package's
|
|
@@ -62,5 +64,5 @@
|
|
|
62
64
|
*/
|
|
63
65
|
export { type AuthFailure, type CallFn, type CallOptions, createCall, type ErrorHints, } from './call.js';
|
|
64
66
|
export { createServer } from './server.js';
|
|
65
|
-
export { ACCOUNT_TOOL_NAMES, registerAccountTools } from './tools.js';
|
|
66
|
-
export {
|
|
67
|
+
export { ACCOUNT_TOOL_NAMES, type AccountToolName, registerAccountTools, TOOLS, type ToolName, } from './tools.js';
|
|
68
|
+
export { annotate, DESCRIPTION_BLOCKS, INSTRUCTION_BLOCKS, PARAM_DESCRIPTIONS, PUBLIC_EXPIRY, SERVER_NAME, type ToolContract, titled, UPLOAD_TOOL_NAME, UPLOAD_TOOL_TITLE, } from './vocabulary.js';
|
package/dist/index.js
CHANGED
|
@@ -26,8 +26,10 @@
|
|
|
26
26
|
* was the same duration written out eight times, `DESCRIPTION_BLOCKS` the
|
|
27
27
|
* fragments two tool descriptions genuinely share, `ACCOUNT_TOOL_NAMES` is
|
|
28
28
|
* what lets the hosted catalogue fence name the fourteen without counting them
|
|
29
|
-
* again, and `
|
|
30
|
-
*
|
|
29
|
+
* again, `TOOLS` and `annotate` replaced four annotation classes plus a
|
|
30
|
+
* hand-annotated upload on each transport plus a separate list for the auth
|
|
31
|
+
* need (three owners for one tool's facts, 1.11.0), and `createServer`
|
|
32
|
+
* deleted three regexes in another repo's build (see below).
|
|
31
33
|
*
|
|
32
34
|
* **`createServer` is exported; the configured `call` is not.** The extension
|
|
33
35
|
* previously reached stdio's composition by REGEX-PATCHING this package's
|
|
@@ -62,5 +64,5 @@
|
|
|
62
64
|
*/
|
|
63
65
|
export { createCall, } from './call.js';
|
|
64
66
|
export { createServer } from './server.js';
|
|
65
|
-
export { ACCOUNT_TOOL_NAMES, registerAccountTools } from './tools.js';
|
|
66
|
-
export {
|
|
67
|
+
export { ACCOUNT_TOOL_NAMES, registerAccountTools, TOOLS, } from './tools.js';
|
|
68
|
+
export { annotate, DESCRIPTION_BLOCKS, INSTRUCTION_BLOCKS, PARAM_DESCRIPTIONS, PUBLIC_EXPIRY, SERVER_NAME, titled, UPLOAD_TOOL_NAME, UPLOAD_TOOL_TITLE, } from './vocabulary.js';
|
package/dist/server.js
CHANGED
|
@@ -2,12 +2,8 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
import { DeploymentVia, MY_API_KEY_URL } from '@shipstatic/types';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { call } from './call.js';
|
|
5
|
-
import { registerAccountTools } from './tools.js';
|
|
6
|
-
import {
|
|
7
|
-
// Destructured so the fifteen registrations below read as they always have.
|
|
8
|
-
// The definitions live in `vocabulary.ts` because the hosted transport speaks
|
|
9
|
-
// the same ones — that file records what is shared, what is not, and why.
|
|
10
|
-
const { CREATE } = ANNOTATIONS;
|
|
5
|
+
import { registerAccountTools, TOOLS } from './tools.js';
|
|
6
|
+
import { annotate, DESCRIPTION_BLOCKS, INSTRUCTION_BLOCKS, PARAM_DESCRIPTIONS, PUBLIC_EXPIRY, SERVER_NAME, titled, UPLOAD_TOOL_NAME, UPLOAD_TOOL_TITLE, } from './vocabulary.js';
|
|
11
7
|
const B = INSTRUCTION_BLOCKS;
|
|
12
8
|
const D = DESCRIPTION_BLOCKS;
|
|
13
9
|
// Composed from the shared blocks plus the two sentences that are genuinely
|
|
@@ -16,7 +12,7 @@ const D = DESCRIPTION_BLOCKS;
|
|
|
16
12
|
// around its own two.
|
|
17
13
|
const INSTRUCTIONS = `${B.opening}
|
|
18
14
|
|
|
19
|
-
To deploy: call ${UPLOAD_TOOL_NAME} with the build output directory path. ${B.
|
|
15
|
+
To deploy: call ${UPLOAD_TOOL_NAME} with the build output directory path. ${B.live} ${B.password}
|
|
20
16
|
|
|
21
17
|
Without SHIP_TOKEN, deployments are public and expire in ${PUBLIC_EXPIRY}. ${B.claim}
|
|
22
18
|
|
|
@@ -41,8 +37,12 @@ export function createServer(ship, options) {
|
|
|
41
37
|
// Deployments
|
|
42
38
|
server.registerTool(UPLOAD_TOOL_NAME, titled({
|
|
43
39
|
title: UPLOAD_TOOL_TITLE,
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
// Describes the tool and instructs nobody: what an agent should DO with
|
|
41
|
+
// the claim URL and a password is the INSTRUCTIONS' job, stated once
|
|
42
|
+
// above. The annotations come from the registry row this transport
|
|
43
|
+
// shares with the hosted one; only the input schema is this door's own.
|
|
44
|
+
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
|
+
annotations: annotate(TOOLS[UPLOAD_TOOL_NAME]),
|
|
46
46
|
inputSchema: {
|
|
47
47
|
path: z
|
|
48
48
|
.string()
|
package/dist/tools.d.ts
CHANGED
|
@@ -40,23 +40,118 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
40
40
|
import type Ship from '@shipstatic/ship';
|
|
41
41
|
import type { CallFn } from './call.js';
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
43
|
+
* THE REGISTRY: one row per tool, every fact about it that a host reads.
|
|
44
|
+
*
|
|
45
|
+
* Fifteen rows, in registration order, `deployments_upload` included even
|
|
46
|
+
* though each transport authors that registration itself: its INPUT differs
|
|
47
|
+
* per transport, its contract does not. Everything else is derived from
|
|
48
|
+
* here. `annotate(row)` is every registration's `annotations`;
|
|
49
|
+
* `ACCOUNT_TOOL_NAMES` is the rows whose `auth` is `required`, and the
|
|
50
|
+
* hosted door reads the same column for `securitySchemes`; the listing
|
|
51
|
+
* repo's justifications are held to the hints these rows produce.
|
|
52
|
+
*
|
|
53
|
+
* The rows are DATA the registrations read, not a table they are generated
|
|
54
|
+
* from, and that is deliberate: a `Record<name, factory>` would cost the
|
|
55
|
+
* zod→handler inference every one-liner below relies on (`({ deployment })
|
|
56
|
+
* => …` is typed from the `inputSchema` literal in the same call). A row
|
|
57
|
+
* without a registration, a registration without a row, and a typo in
|
|
58
|
+
* either all turn `tests/server.test.ts` red, through a real `tools/list`.
|
|
59
|
+
*
|
|
60
|
+
* Two rows are worth a second look, because their names suggest otherwise:
|
|
61
|
+
* `domains_dns` READS the provider the platform recorded when the domain
|
|
62
|
+
* was created (nothing is looked up on call), and `domains_validate` and
|
|
63
|
+
* `domains_share` persist nothing.
|
|
64
|
+
*/
|
|
65
|
+
export declare const TOOLS: {
|
|
66
|
+
readonly deployments_upload: {
|
|
67
|
+
readonly auth: "optional";
|
|
68
|
+
readonly mutation: "add";
|
|
69
|
+
readonly reach: "public";
|
|
70
|
+
};
|
|
71
|
+
readonly deployments_list: {
|
|
72
|
+
readonly auth: "required";
|
|
73
|
+
readonly mutation: "none";
|
|
74
|
+
readonly reach: "account";
|
|
75
|
+
};
|
|
76
|
+
readonly deployments_get: {
|
|
77
|
+
readonly auth: "required";
|
|
78
|
+
readonly mutation: "none";
|
|
79
|
+
readonly reach: "account";
|
|
80
|
+
};
|
|
81
|
+
readonly deployments_set: {
|
|
82
|
+
readonly auth: "required";
|
|
83
|
+
readonly mutation: "replace";
|
|
84
|
+
readonly reach: "account";
|
|
85
|
+
};
|
|
86
|
+
readonly deployments_delete: {
|
|
87
|
+
readonly auth: "required";
|
|
88
|
+
readonly mutation: "remove";
|
|
89
|
+
readonly reach: "public";
|
|
90
|
+
};
|
|
91
|
+
readonly domains_set: {
|
|
92
|
+
readonly auth: "required";
|
|
93
|
+
readonly mutation: "replace";
|
|
94
|
+
readonly reach: "public";
|
|
95
|
+
};
|
|
96
|
+
readonly domains_list: {
|
|
97
|
+
readonly auth: "required";
|
|
98
|
+
readonly mutation: "none";
|
|
99
|
+
readonly reach: "account";
|
|
100
|
+
};
|
|
101
|
+
readonly domains_get: {
|
|
102
|
+
readonly auth: "required";
|
|
103
|
+
readonly mutation: "none";
|
|
104
|
+
readonly reach: "account";
|
|
105
|
+
};
|
|
106
|
+
readonly domains_records: {
|
|
107
|
+
readonly auth: "required";
|
|
108
|
+
readonly mutation: "none";
|
|
109
|
+
readonly reach: "account";
|
|
110
|
+
};
|
|
111
|
+
readonly domains_dns: {
|
|
112
|
+
readonly auth: "required";
|
|
113
|
+
readonly mutation: "none";
|
|
114
|
+
readonly reach: "account";
|
|
115
|
+
};
|
|
116
|
+
readonly domains_share: {
|
|
117
|
+
readonly auth: "required";
|
|
118
|
+
readonly mutation: "none";
|
|
119
|
+
readonly reach: "account";
|
|
120
|
+
};
|
|
121
|
+
readonly domains_validate: {
|
|
122
|
+
readonly auth: "required";
|
|
123
|
+
readonly mutation: "none";
|
|
124
|
+
readonly reach: "account";
|
|
125
|
+
};
|
|
126
|
+
readonly domains_verify: {
|
|
127
|
+
readonly auth: "required";
|
|
128
|
+
readonly mutation: "add";
|
|
129
|
+
readonly reach: "public";
|
|
130
|
+
};
|
|
131
|
+
readonly domains_delete: {
|
|
132
|
+
readonly auth: "required";
|
|
133
|
+
readonly mutation: "remove";
|
|
134
|
+
readonly reach: "public";
|
|
135
|
+
};
|
|
136
|
+
readonly whoami: {
|
|
137
|
+
readonly auth: "required";
|
|
138
|
+
readonly mutation: "none";
|
|
139
|
+
readonly reach: "account";
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
export type ToolName = keyof typeof TOOLS;
|
|
143
|
+
/** The names whose row says an account is required. */
|
|
144
|
+
export type AccountToolName = {
|
|
145
|
+
[N in ToolName]: (typeof TOOLS)[N]['auth'] extends 'required' ? N : never;
|
|
146
|
+
}[ToolName];
|
|
147
|
+
/**
|
|
148
|
+
* The fourteen, derived from the registry rather than listed beside it.
|
|
44
149
|
*
|
|
45
150
|
* Exported so a second transport can state its expected catalogue as
|
|
46
|
-
* `[UPLOAD_TOOL_NAME, ...ACCOUNT_TOOL_NAMES]` instead of
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* **Deliberately a list beside the registrations rather than a table they are
|
|
52
|
-
* generated from.** A `Record<name, factory>` would make the pairing
|
|
53
|
-
* structural, and it would cost the zod→handler inference every one of the
|
|
54
|
-
* fourteen one-liners below relies on: `({ deployment }) => …` is typed today
|
|
55
|
-
* from the `inputSchema` literal in the same call, and a loop over a
|
|
56
|
-
* heterogeneous table cannot correlate the two. The same guarantee costs
|
|
57
|
-
* nothing as a set comparison, and `tests/server.test.ts` makes it — through a
|
|
58
|
-
* real `tools/list`, so a registration without a name, a name without a
|
|
59
|
-
* registration, and a typo in either all turn it red.
|
|
151
|
+
* `[UPLOAD_TOOL_NAME, ...ACCOUNT_TOOL_NAMES]` instead of counting to fifteen
|
|
152
|
+
* in a second repo. It was a hand-written list until 1.11.0, which made the
|
|
153
|
+
* auth need a second owner beside the annotations; a row is now the only
|
|
154
|
+
* place a tool's account requirement is stated.
|
|
60
155
|
*/
|
|
61
|
-
export declare const ACCOUNT_TOOL_NAMES:
|
|
156
|
+
export declare const ACCOUNT_TOOL_NAMES: AccountToolName[];
|
|
62
157
|
export declare function registerAccountTools(server: McpServer, ship: Ship, call: CallFn): void;
|
package/dist/tools.js
CHANGED
|
@@ -37,43 +37,70 @@
|
|
|
37
37
|
* every tool, so the next one cannot be added without one.
|
|
38
38
|
*/
|
|
39
39
|
import { z } from 'zod';
|
|
40
|
-
import {
|
|
41
|
-
const { READ, WRITE, DESTRUCTIVE } = ANNOTATIONS;
|
|
40
|
+
import { annotate, titled, UPLOAD_TOOL_NAME } from './vocabulary.js';
|
|
42
41
|
/**
|
|
43
|
-
*
|
|
42
|
+
* THE REGISTRY: one row per tool, every fact about it that a host reads.
|
|
43
|
+
*
|
|
44
|
+
* Fifteen rows, in registration order, `deployments_upload` included even
|
|
45
|
+
* though each transport authors that registration itself: its INPUT differs
|
|
46
|
+
* per transport, its contract does not. Everything else is derived from
|
|
47
|
+
* here. `annotate(row)` is every registration's `annotations`;
|
|
48
|
+
* `ACCOUNT_TOOL_NAMES` is the rows whose `auth` is `required`, and the
|
|
49
|
+
* hosted door reads the same column for `securitySchemes`; the listing
|
|
50
|
+
* repo's justifications are held to the hints these rows produce.
|
|
51
|
+
*
|
|
52
|
+
* The rows are DATA the registrations read, not a table they are generated
|
|
53
|
+
* from, and that is deliberate: a `Record<name, factory>` would cost the
|
|
54
|
+
* zod→handler inference every one-liner below relies on (`({ deployment })
|
|
55
|
+
* => …` is typed from the `inputSchema` literal in the same call). A row
|
|
56
|
+
* without a registration, a registration without a row, and a typo in
|
|
57
|
+
* either all turn `tests/server.test.ts` red, through a real `tools/list`.
|
|
58
|
+
*
|
|
59
|
+
* Two rows are worth a second look, because their names suggest otherwise:
|
|
60
|
+
* `domains_dns` READS the provider the platform recorded when the domain
|
|
61
|
+
* was created (nothing is looked up on call), and `domains_validate` and
|
|
62
|
+
* `domains_share` persist nothing.
|
|
63
|
+
*/
|
|
64
|
+
export const TOOLS = {
|
|
65
|
+
[UPLOAD_TOOL_NAME]: { auth: 'optional', mutation: 'add', reach: 'public' },
|
|
66
|
+
deployments_list: { auth: 'required', mutation: 'none', reach: 'account' },
|
|
67
|
+
deployments_get: { auth: 'required', mutation: 'none', reach: 'account' },
|
|
68
|
+
deployments_set: { auth: 'required', mutation: 'replace', reach: 'account' },
|
|
69
|
+
deployments_delete: { auth: 'required', mutation: 'remove', reach: 'public' },
|
|
70
|
+
domains_set: { auth: 'required', mutation: 'replace', reach: 'public' },
|
|
71
|
+
domains_list: { auth: 'required', mutation: 'none', reach: 'account' },
|
|
72
|
+
domains_get: { auth: 'required', mutation: 'none', reach: 'account' },
|
|
73
|
+
domains_records: { auth: 'required', mutation: 'none', reach: 'account' },
|
|
74
|
+
domains_dns: { auth: 'required', mutation: 'none', reach: 'account' },
|
|
75
|
+
domains_share: { auth: 'required', mutation: 'none', reach: 'account' },
|
|
76
|
+
domains_validate: { auth: 'required', mutation: 'none', reach: 'account' },
|
|
77
|
+
domains_verify: { auth: 'required', mutation: 'add', reach: 'public' },
|
|
78
|
+
domains_delete: { auth: 'required', mutation: 'remove', reach: 'public' },
|
|
79
|
+
whoami: { auth: 'required', mutation: 'none', reach: 'account' },
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* The fourteen, derived from the registry rather than listed beside it.
|
|
44
83
|
*
|
|
45
84
|
* Exported so a second transport can state its expected catalogue as
|
|
46
|
-
* `[UPLOAD_TOOL_NAME, ...ACCOUNT_TOOL_NAMES]` instead of
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
85
|
+
* `[UPLOAD_TOOL_NAME, ...ACCOUNT_TOOL_NAMES]` instead of counting to fifteen
|
|
86
|
+
* in a second repo. It was a hand-written list until 1.11.0, which made the
|
|
87
|
+
* auth need a second owner beside the annotations; a row is now the only
|
|
88
|
+
* place a tool's account requirement is stated.
|
|
89
|
+
*/
|
|
90
|
+
export const ACCOUNT_TOOL_NAMES = Object.keys(TOOLS).filter((name) => TOOLS[name].auth === 'required');
|
|
91
|
+
/**
|
|
92
|
+
* What `whoami` answers: exactly the keys its description names.
|
|
50
93
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* nothing as a set comparison, and `tests/server.test.ts` makes it — through a
|
|
58
|
-
* real `tools/list`, so a registration without a name, a name without a
|
|
59
|
-
* registration, and a typo in either all turn it red.
|
|
94
|
+
* `Account` also carries billing state, the API-key hint, the picture and
|
|
95
|
+
* 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; this
|
|
97
|
+
* is the one tool where the wire's own entity said more than the sentence.
|
|
98
|
+
* `suspended` is deliberately out: it means every write is refused, and the
|
|
99
|
+
* refusal says so itself at the moment it matters.
|
|
60
100
|
*/
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
'deployments_set',
|
|
65
|
-
'deployments_delete',
|
|
66
|
-
'domains_set',
|
|
67
|
-
'domains_list',
|
|
68
|
-
'domains_get',
|
|
69
|
-
'domains_records',
|
|
70
|
-
'domains_dns',
|
|
71
|
-
'domains_share',
|
|
72
|
-
'domains_validate',
|
|
73
|
-
'domains_verify',
|
|
74
|
-
'domains_delete',
|
|
75
|
-
'whoami',
|
|
76
|
-
];
|
|
101
|
+
function accountSummary({ email, name, plan, usage, caps }) {
|
|
102
|
+
return { email, name, plan, usage, caps };
|
|
103
|
+
}
|
|
77
104
|
/**
|
|
78
105
|
* The pagination surface, shared by every list tool because it is one
|
|
79
106
|
* contract, not two. A list answers `{<collection>, cursor}` and nothing
|
|
@@ -106,13 +133,13 @@ export function registerAccountTools(server, ship, call) {
|
|
|
106
133
|
server.registerTool('deployments_list', titled({
|
|
107
134
|
title: 'List Deployments',
|
|
108
135
|
description: `List all deployments with their URLs, status, labels, and password protection state.${PAGING_NOTE}`,
|
|
109
|
-
annotations:
|
|
136
|
+
annotations: annotate(TOOLS.deployments_list),
|
|
110
137
|
inputSchema: PAGINATION_INPUT,
|
|
111
138
|
}), ({ limit, cursor }) => call(() => ship.deployments.list({ limit, cursor })));
|
|
112
139
|
server.registerTool('deployments_get', titled({
|
|
113
140
|
title: 'Get Deployment',
|
|
114
141
|
description: 'Get deployment details including URL, status, file count, size, labels, and password protection state.',
|
|
115
|
-
annotations:
|
|
142
|
+
annotations: annotate(TOOLS.deployments_get),
|
|
116
143
|
inputSchema: {
|
|
117
144
|
deployment: z
|
|
118
145
|
.string()
|
|
@@ -122,7 +149,7 @@ export function registerAccountTools(server, ship, call) {
|
|
|
122
149
|
server.registerTool('deployments_set', titled({
|
|
123
150
|
title: 'Update Deployment Labels',
|
|
124
151
|
description: 'Update deployment labels. Replaces all existing labels.',
|
|
125
|
-
annotations:
|
|
152
|
+
annotations: annotate(TOOLS.deployments_set),
|
|
126
153
|
inputSchema: {
|
|
127
154
|
deployment: z
|
|
128
155
|
.string()
|
|
@@ -134,8 +161,8 @@ export function registerAccountTools(server, ship, call) {
|
|
|
134
161
|
}), ({ deployment, labels }) => call(() => ship.deployments.set(deployment, { labels })));
|
|
135
162
|
server.registerTool('deployments_delete', titled({
|
|
136
163
|
title: 'Delete Deployment',
|
|
137
|
-
description: 'Permanently
|
|
138
|
-
annotations:
|
|
164
|
+
description: 'Permanently deletes a deployment and its files.',
|
|
165
|
+
annotations: annotate(TOOLS.deployments_delete),
|
|
139
166
|
inputSchema: {
|
|
140
167
|
deployment: z
|
|
141
168
|
.string()
|
|
@@ -145,8 +172,8 @@ export function registerAccountTools(server, ship, call) {
|
|
|
145
172
|
// Domains
|
|
146
173
|
server.registerTool('domains_set', titled({
|
|
147
174
|
title: 'Connect Custom Domain',
|
|
148
|
-
description: 'Create or update a custom domain. Can reserve a name (omit deployment), link it to a deployment, switch deployments, or update labels.
|
|
149
|
-
annotations:
|
|
175
|
+
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
|
+
annotations: annotate(TOOLS.domains_set),
|
|
150
177
|
inputSchema: {
|
|
151
178
|
domain: z.string().describe('Domain name (e.g. "www.example.com" or "blog.example.com")'),
|
|
152
179
|
deployment: z
|
|
@@ -162,13 +189,13 @@ export function registerAccountTools(server, ship, call) {
|
|
|
162
189
|
server.registerTool('domains_list', titled({
|
|
163
190
|
title: 'List Domains',
|
|
164
191
|
description: `List all domains with their URLs, linked deployment, and verification status.${PAGING_NOTE}`,
|
|
165
|
-
annotations:
|
|
192
|
+
annotations: annotate(TOOLS.domains_list),
|
|
166
193
|
inputSchema: PAGINATION_INPUT,
|
|
167
194
|
}), ({ limit, cursor }) => call(() => ship.domains.list({ limit, cursor })));
|
|
168
195
|
server.registerTool('domains_get', titled({
|
|
169
196
|
title: 'Get Domain',
|
|
170
197
|
description: 'Get domain details including URL, linked deployment, verification status, and labels.',
|
|
171
|
-
annotations:
|
|
198
|
+
annotations: annotate(TOOLS.domains_get),
|
|
172
199
|
inputSchema: {
|
|
173
200
|
domain: z
|
|
174
201
|
.string()
|
|
@@ -177,8 +204,8 @@ export function registerAccountTools(server, ship, call) {
|
|
|
177
204
|
}), ({ domain }) => call(() => ship.domains.get(domain)));
|
|
178
205
|
server.registerTool('domains_records', titled({
|
|
179
206
|
title: 'Get DNS Records',
|
|
180
|
-
description:
|
|
181
|
-
annotations:
|
|
207
|
+
description: "Returns the DNS records to configure at the domain's DNS provider. Call after domains_set.",
|
|
208
|
+
annotations: annotate(TOOLS.domains_records),
|
|
182
209
|
inputSchema: {
|
|
183
210
|
domain: z
|
|
184
211
|
.string()
|
|
@@ -186,19 +213,19 @@ export function registerAccountTools(server, ship, call) {
|
|
|
186
213
|
},
|
|
187
214
|
}), ({ domain }) => call(() => ship.domains.records(domain)));
|
|
188
215
|
server.registerTool('domains_dns', titled({
|
|
189
|
-
title: '
|
|
190
|
-
description: '
|
|
191
|
-
annotations:
|
|
216
|
+
title: 'Get DNS Provider',
|
|
217
|
+
description: 'Returns the DNS provider recorded for the domain, if known (e.g. Cloudflare, Namecheap): where its DNS records are configured.',
|
|
218
|
+
annotations: annotate(TOOLS.domains_dns),
|
|
192
219
|
inputSchema: {
|
|
193
220
|
domain: z
|
|
194
221
|
.string()
|
|
195
|
-
.describe('Domain name
|
|
222
|
+
.describe('Domain name (e.g. "www.example.com"). Must be a domain previously created with domains_set.'),
|
|
196
223
|
},
|
|
197
224
|
}), ({ domain }) => call(() => ship.domains.dns(domain)));
|
|
198
225
|
server.registerTool('domains_share', titled({
|
|
199
226
|
title: 'Share DNS Setup',
|
|
200
|
-
description:
|
|
201
|
-
annotations:
|
|
227
|
+
description: "Returns a shareable DNS setup URL that needs no API key, for whoever manages the domain's DNS.",
|
|
228
|
+
annotations: annotate(TOOLS.domains_share),
|
|
202
229
|
inputSchema: {
|
|
203
230
|
domain: z
|
|
204
231
|
.string()
|
|
@@ -208,7 +235,7 @@ export function registerAccountTools(server, ship, call) {
|
|
|
208
235
|
server.registerTool('domains_validate', titled({
|
|
209
236
|
title: 'Check Domain Availability',
|
|
210
237
|
description: 'Check if a domain name is valid and available before creating it. Returns the normalized form and availability.',
|
|
211
|
-
annotations:
|
|
238
|
+
annotations: annotate(TOOLS.domains_validate),
|
|
212
239
|
inputSchema: {
|
|
213
240
|
domain: z
|
|
214
241
|
.string()
|
|
@@ -218,7 +245,7 @@ export function registerAccountTools(server, ship, call) {
|
|
|
218
245
|
server.registerTool('domains_verify', titled({
|
|
219
246
|
title: 'Verify Domain DNS',
|
|
220
247
|
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.',
|
|
221
|
-
annotations:
|
|
248
|
+
annotations: annotate(TOOLS.domains_verify),
|
|
222
249
|
inputSchema: {
|
|
223
250
|
domain: z
|
|
224
251
|
.string()
|
|
@@ -227,8 +254,8 @@ export function registerAccountTools(server, ship, call) {
|
|
|
227
254
|
}), ({ domain }) => call(() => ship.domains.verify(domain)));
|
|
228
255
|
server.registerTool('domains_delete', titled({
|
|
229
256
|
title: 'Delete Domain',
|
|
230
|
-
description: 'Permanently
|
|
231
|
-
annotations:
|
|
257
|
+
description: 'Permanently deletes a domain.',
|
|
258
|
+
annotations: annotate(TOOLS.domains_delete),
|
|
232
259
|
inputSchema: {
|
|
233
260
|
domain: z.string().describe('Domain name to delete (e.g. "www.example.com")'),
|
|
234
261
|
},
|
|
@@ -236,7 +263,7 @@ export function registerAccountTools(server, ship, call) {
|
|
|
236
263
|
// Account
|
|
237
264
|
server.registerTool('whoami', titled({
|
|
238
265
|
title: 'Show Account',
|
|
239
|
-
description:
|
|
240
|
-
annotations:
|
|
241
|
-
}), () => call(() => ship.whoami()));
|
|
266
|
+
description: "Returns the account's email, name, plan, current usage and plan caps.",
|
|
267
|
+
annotations: annotate(TOOLS.whoami),
|
|
268
|
+
}), () => call(() => ship.whoami().then(accountSummary)));
|
|
242
269
|
}
|
package/dist/vocabulary.d.ts
CHANGED
|
@@ -103,43 +103,53 @@ export declare const UPLOAD_TOOL_TITLE = "Deploy Static Site";
|
|
|
103
103
|
*/
|
|
104
104
|
export declare const PUBLIC_EXPIRY: string;
|
|
105
105
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
|
|
106
|
+
* What is true of one tool, on the three axes a host reads.
|
|
107
|
+
*
|
|
108
|
+
* One row per tool lives in the registry (`tools.ts`, `TOOLS`), and every
|
|
109
|
+
* other statement about the tool is derived from it: the MCP annotations
|
|
110
|
+
* (`annotate`), the account-tool set (and so the hosted door's
|
|
111
|
+
* `securitySchemes` and challenge scope), the listing justifications in
|
|
112
|
+
* `integrations/gpt`, and the catalogue tests. Until 1.11.0 these facts had
|
|
113
|
+
* three owners: four annotation classes here, a hand-written name list for
|
|
114
|
+
* the auth need, and the upload tool annotated by hand on both transports.
|
|
115
|
+
* Three owners for facts about one tool is how the classes came to spread
|
|
116
|
+
* `openWorldHint: true` over ten reads that touch nothing public.
|
|
117
|
+
*/
|
|
118
|
+
export interface ToolContract {
|
|
119
|
+
/** `optional`: works without an account, does more with one. */
|
|
120
|
+
auth: 'optional' | 'required';
|
|
121
|
+
/**
|
|
122
|
+
* What a call does to the platform's state. `add` creates something new
|
|
123
|
+
* on every call; `replace` overwrites what is there; `remove` deletes it.
|
|
124
|
+
*/
|
|
125
|
+
mutation: 'none' | 'add' | 'replace' | 'remove';
|
|
126
|
+
/** Whether the call changes public internet state, or only the account's. */
|
|
127
|
+
reach: 'account' | 'public';
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The MCP annotations a tool's contract implies. An agent reads them to
|
|
131
|
+
* decide whether it may call speculatively (`readOnlyHint`), whether it must
|
|
132
|
+
* confirm with the user first (`destructiveHint`), whether a retry is free
|
|
133
|
+
* (`idempotentHint`), and whether the call reaches beyond the account
|
|
134
|
+
* (`openWorldHint`). Both hosts read the same four: Anthropic's review asks
|
|
135
|
+
* for an accurate `readOnlyHint` and `destructiveHint`, OpenAI additionally
|
|
136
|
+
* reads `openWorldHint` as "can change public state".
|
|
137
|
+
*
|
|
138
|
+
* **`idempotentHint` is promised only for a remove**, the one mutation where
|
|
139
|
+
* a repeat is MEASURED to have no further effect (both deletes are fenced in
|
|
140
|
+
* the API: a repeat neither flips state nor writes an audit row). A replace
|
|
141
|
+
* writes an activity row per call, and `domains_set` notifies and may enqueue
|
|
142
|
+
* DNS work besides, so it is not claimed. An add creates on every call, and
|
|
143
|
+
* `idempotencyKey` cannot rescue the claim: that property is conditional on
|
|
144
|
+
* an argument the caller may not pass, while the annotation is static per
|
|
145
|
+
* tool. Reads carry none because the spec defines the hint only for tools
|
|
146
|
+
* that mutate.
|
|
118
147
|
*/
|
|
119
|
-
export declare
|
|
120
|
-
readonly
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
readonly idempotentHint: true;
|
|
125
|
-
};
|
|
126
|
-
readonly CREATE: {
|
|
127
|
-
readonly openWorldHint: true;
|
|
128
|
-
readonly readOnlyHint: false;
|
|
129
|
-
readonly destructiveHint: false;
|
|
130
|
-
};
|
|
131
|
-
readonly WRITE: {
|
|
132
|
-
readonly openWorldHint: true;
|
|
133
|
-
readonly readOnlyHint: false;
|
|
134
|
-
readonly destructiveHint: false;
|
|
135
|
-
readonly idempotentHint: true;
|
|
136
|
-
};
|
|
137
|
-
readonly DESTRUCTIVE: {
|
|
138
|
-
readonly openWorldHint: true;
|
|
139
|
-
readonly readOnlyHint: false;
|
|
140
|
-
readonly destructiveHint: true;
|
|
141
|
-
readonly idempotentHint: true;
|
|
142
|
-
};
|
|
148
|
+
export declare function annotate({ mutation, reach }: ToolContract): {
|
|
149
|
+
readonly openWorldHint: boolean;
|
|
150
|
+
readonly idempotentHint?: boolean | undefined;
|
|
151
|
+
readonly readOnlyHint: boolean;
|
|
152
|
+
readonly destructiveHint: boolean;
|
|
143
153
|
};
|
|
144
154
|
/**
|
|
145
155
|
* One title, two wire slots: project a tool config's top-level `title` into
|
|
@@ -178,7 +188,19 @@ export declare function titled<C extends {
|
|
|
178
188
|
*/
|
|
179
189
|
export declare const INSTRUCTION_BLOCKS: {
|
|
180
190
|
readonly opening: "ShipStatic deploys static websites instantly. Free, no account required.";
|
|
181
|
-
readonly
|
|
191
|
+
readonly live: "The site is live immediately.";
|
|
192
|
+
/**
|
|
193
|
+
* Two sentences in one block, and a block of its own rather than a clause
|
|
194
|
+
* of `live`: a door that collects no visitor password (the hosted `/gpt`
|
|
195
|
+
* door, by OpenAI's data rule) composes `live` without this.
|
|
196
|
+
*
|
|
197
|
+
* The read-back rule lives HERE and not in the upload description: a
|
|
198
|
+
* password the user never sees locks them out, and `instructions` is the
|
|
199
|
+
* one place a server may say what an agent should do. A tool description
|
|
200
|
+
* describes the tool; both listing reviews reject one that instructs the
|
|
201
|
+
* model, and the catalogue tests grep for it.
|
|
202
|
+
*/
|
|
203
|
+
readonly password: "To make the site private, pass `password`; visitors must unlock before viewing, including on any custom domains pointing at it. Tell the user any password you set, since one they never see locks them out.";
|
|
182
204
|
readonly claim: "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.";
|
|
183
205
|
readonly conceptsHeader: "Concepts:";
|
|
184
206
|
readonly deploymentConcept: "- Deployment: an immutable set of files with an instant URL (e.g. happy-cat-abc1234.shipstatic.com). No setup needed.";
|
|
@@ -201,8 +223,12 @@ export declare const INSTRUCTION_BLOCKS: {
|
|
|
201
223
|
export declare const DESCRIPTION_BLOCKS: {
|
|
202
224
|
/** The no-account promise, mid-sentence in both openings. */
|
|
203
225
|
readonly free: "free, no account or API key required";
|
|
204
|
-
/**
|
|
205
|
-
|
|
226
|
+
/**
|
|
227
|
+
* The option, stated as a capability. The read-back rule that used to
|
|
228
|
+
* follow it ("always show the password to the user") is behavioural
|
|
229
|
+
* guidance, which belongs to `INSTRUCTION_BLOCKS.password`.
|
|
230
|
+
*/
|
|
231
|
+
readonly password: "Pass `password` to make the site private.";
|
|
206
232
|
};
|
|
207
233
|
/**
|
|
208
234
|
* Deploy-parameter descriptions shared by both transports.
|
package/dist/vocabulary.js
CHANGED
|
@@ -104,27 +104,33 @@ export const UPLOAD_TOOL_TITLE = 'Deploy Static Site';
|
|
|
104
104
|
* sentence someone must rewrite, not a number that silently rounds.
|
|
105
105
|
*/
|
|
106
106
|
export const PUBLIC_EXPIRY = `${PUBLIC_DEPLOYMENT_TTL_SECONDS / 86_400} days`;
|
|
107
|
-
const OPEN_WORLD = { openWorldHint: true };
|
|
108
107
|
/**
|
|
109
|
-
* MCP
|
|
110
|
-
* whether it may call speculatively (`readOnlyHint`), whether
|
|
111
|
-
* (`
|
|
112
|
-
* (`
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* the
|
|
120
|
-
*
|
|
108
|
+
* The MCP annotations a tool's contract implies. An agent reads them to
|
|
109
|
+
* decide whether it may call speculatively (`readOnlyHint`), whether it must
|
|
110
|
+
* confirm with the user first (`destructiveHint`), whether a retry is free
|
|
111
|
+
* (`idempotentHint`), and whether the call reaches beyond the account
|
|
112
|
+
* (`openWorldHint`). Both hosts read the same four: Anthropic's review asks
|
|
113
|
+
* for an accurate `readOnlyHint` and `destructiveHint`, OpenAI additionally
|
|
114
|
+
* reads `openWorldHint` as "can change public state".
|
|
115
|
+
*
|
|
116
|
+
* **`idempotentHint` is promised only for a remove**, the one mutation where
|
|
117
|
+
* a repeat is MEASURED to have no further effect (both deletes are fenced in
|
|
118
|
+
* the API: a repeat neither flips state nor writes an audit row). A replace
|
|
119
|
+
* writes an activity row per call, and `domains_set` notifies and may enqueue
|
|
120
|
+
* DNS work besides, so it is not claimed. An add creates on every call, and
|
|
121
|
+
* `idempotencyKey` cannot rescue the claim: that property is conditional on
|
|
122
|
+
* an argument the caller may not pass, while the annotation is static per
|
|
123
|
+
* tool. Reads carry none because the spec defines the hint only for tools
|
|
124
|
+
* that mutate.
|
|
121
125
|
*/
|
|
122
|
-
export
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
export function annotate({ mutation, reach }) {
|
|
127
|
+
return {
|
|
128
|
+
readOnlyHint: mutation === 'none',
|
|
129
|
+
destructiveHint: mutation === 'replace' || mutation === 'remove',
|
|
130
|
+
...(mutation === 'remove' ? { idempotentHint: true } : {}),
|
|
131
|
+
openWorldHint: reach === 'public',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
128
134
|
/**
|
|
129
135
|
* One title, two wire slots: project a tool config's top-level `title` into
|
|
130
136
|
* `annotations.title` at registration.
|
|
@@ -161,7 +167,19 @@ export function titled(config) {
|
|
|
161
167
|
*/
|
|
162
168
|
export const INSTRUCTION_BLOCKS = {
|
|
163
169
|
opening: 'ShipStatic deploys static websites instantly. Free, no account required.',
|
|
164
|
-
|
|
170
|
+
live: 'The site is live immediately.',
|
|
171
|
+
/**
|
|
172
|
+
* Two sentences in one block, and a block of its own rather than a clause
|
|
173
|
+
* of `live`: a door that collects no visitor password (the hosted `/gpt`
|
|
174
|
+
* door, by OpenAI's data rule) composes `live` without this.
|
|
175
|
+
*
|
|
176
|
+
* The read-back rule lives HERE and not in the upload description: a
|
|
177
|
+
* password the user never sees locks them out, and `instructions` is the
|
|
178
|
+
* one place a server may say what an agent should do. A tool description
|
|
179
|
+
* describes the tool; both listing reviews reject one that instructs the
|
|
180
|
+
* model, and the catalogue tests grep for it.
|
|
181
|
+
*/
|
|
182
|
+
password: 'To make the site private, pass `password`; visitors must unlock before viewing, including on any custom domains pointing at it. Tell the user any password you set, since one they never see locks them out.',
|
|
165
183
|
claim: '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.',
|
|
166
184
|
conceptsHeader: 'Concepts:',
|
|
167
185
|
deploymentConcept: '- Deployment: an immutable set of files with an instant URL (e.g. happy-cat-abc1234.shipstatic.com). No setup needed.',
|
|
@@ -184,8 +202,12 @@ export const INSTRUCTION_BLOCKS = {
|
|
|
184
202
|
export const DESCRIPTION_BLOCKS = {
|
|
185
203
|
/** The no-account promise, mid-sentence in both openings. */
|
|
186
204
|
free: 'free, no account or API key required',
|
|
187
|
-
/**
|
|
188
|
-
|
|
205
|
+
/**
|
|
206
|
+
* The option, stated as a capability. The read-back rule that used to
|
|
207
|
+
* follow it ("always show the password to the user") is behavioural
|
|
208
|
+
* guidance, which belongs to `INSTRUCTION_BLOCKS.password`.
|
|
209
|
+
*/
|
|
210
|
+
password: 'Pass `password` to make the site private.',
|
|
189
211
|
};
|
|
190
212
|
/**
|
|
191
213
|
* Deploy-parameter descriptions shared by both transports.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipstatic/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0-beta.2",
|
|
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",
|