@shipstatic/mcp 1.0.0-beta.0 → 1.0.0-beta.10
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 +13 -3
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +42 -0
- package/dist/call.d.ts +50 -1
- package/dist/call.js +75 -14
- package/dist/index.d.ts +64 -2
- package/dist/index.js +64 -22
- package/dist/server.d.ts +34 -1
- package/dist/server.js +37 -164
- package/dist/tools.d.ts +53 -0
- package/dist/tools.js +219 -0
- package/dist/vocabulary.d.ts +183 -0
- package/dist/vocabulary.js +167 -0
- package/package.json +16 -4
package/dist/server.js
CHANGED
|
@@ -1,186 +1,59 @@
|
|
|
1
|
-
import { createRequire } from 'node:module';
|
|
2
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
-
import {
|
|
2
|
+
import { DeploymentVia } from '@shipstatic/types';
|
|
4
3
|
import { z } from 'zod';
|
|
5
4
|
import { call } from './call.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
...OPEN_WORLD,
|
|
20
|
-
};
|
|
21
|
-
const DESTRUCTIVE = {
|
|
22
|
-
readOnlyHint: false,
|
|
23
|
-
destructiveHint: true,
|
|
24
|
-
idempotentHint: true,
|
|
25
|
-
...OPEN_WORLD,
|
|
26
|
-
};
|
|
27
|
-
const INSTRUCTIONS = `ShipStatic deploys static websites instantly. Free, no account required.
|
|
5
|
+
import { registerAccountTools } from './tools.js';
|
|
6
|
+
import { ANNOTATIONS, DESCRIPTION_BLOCKS, INSTRUCTION_BLOCKS, PARAM_DESCRIPTIONS, PUBLIC_EXPIRY, SERVER_NAME, UPLOAD_TOOL_NAME, } from './vocabulary.js';
|
|
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;
|
|
11
|
+
const B = INSTRUCTION_BLOCKS;
|
|
12
|
+
const D = DESCRIPTION_BLOCKS;
|
|
13
|
+
// Composed from the shared blocks plus the two sentences that are genuinely
|
|
14
|
+
// stdio's: how files arrive (a filesystem path) and how a caller
|
|
15
|
+
// authenticates (`SHIP_TOKEN`). The hosted transport composes the same blocks
|
|
16
|
+
// around its own two.
|
|
17
|
+
const INSTRUCTIONS = `${B.opening}
|
|
28
18
|
|
|
29
|
-
To deploy: call
|
|
19
|
+
To deploy: call ${UPLOAD_TOOL_NAME} with the build output directory path. ${B.liveAndPassword}
|
|
30
20
|
|
|
31
|
-
Without SHIP_TOKEN, deployments are public and expire in
|
|
21
|
+
Without SHIP_TOKEN, deployments are public and expire in ${PUBLIC_EXPIRY}. ${B.claim}
|
|
32
22
|
|
|
33
23
|
With SHIP_TOKEN configured, deployments go to the user's account and never expire. Listing, managing, and domain operations also require SHIP_TOKEN.
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
25
|
+
${B.conceptsHeader}
|
|
26
|
+
${B.deploymentConcept}
|
|
27
|
+
${B.domainConcept}
|
|
38
28
|
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
${B.domainWorkflow}`;
|
|
30
|
+
/**
|
|
31
|
+
* Builds the stdio server's full 15-tool surface over an injected client.
|
|
32
|
+
*/
|
|
33
|
+
export function createServer(ship, options) {
|
|
34
|
+
const { version, via = DeploymentVia.MCP } = options;
|
|
41
35
|
const server = new McpServer({
|
|
42
|
-
name:
|
|
36
|
+
name: SERVER_NAME,
|
|
43
37
|
version,
|
|
44
38
|
}, {
|
|
45
39
|
instructions: INSTRUCTIONS,
|
|
46
40
|
});
|
|
47
41
|
// Deployments
|
|
48
|
-
server.registerTool(
|
|
49
|
-
description:
|
|
42
|
+
server.registerTool(UPLOAD_TOOL_NAME, {
|
|
43
|
+
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}`,
|
|
50
44
|
annotations: CREATE,
|
|
51
45
|
inputSchema: {
|
|
52
46
|
path: z
|
|
53
47
|
.string()
|
|
54
48
|
.describe('Absolute path to the build output directory to deploy (e.g. "/Users/me/project/dist")'),
|
|
55
|
-
labels: z
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}, ({ path, labels, password }) => call(() => ship.deployments.upload(path, { labels, password, via: 'mcp' })));
|
|
65
|
-
server.registerTool('deployments_list', {
|
|
66
|
-
description: 'List all deployments with their URLs, status, labels, and password protection state.',
|
|
67
|
-
annotations: READ,
|
|
68
|
-
}, () => call(() => ship.deployments.list()));
|
|
69
|
-
server.registerTool('deployments_get', {
|
|
70
|
-
description: 'Get deployment details including URL, status, file count, size, labels, and password protection state.',
|
|
71
|
-
annotations: READ,
|
|
72
|
-
inputSchema: {
|
|
73
|
-
deployment: z
|
|
74
|
-
.string()
|
|
75
|
-
.describe('Deployment hostname (e.g. "happy-cat-abc1234.shipstatic.com"). Returned by deployments_upload or deployments_list.'),
|
|
76
|
-
},
|
|
77
|
-
}, ({ deployment }) => call(() => ship.deployments.get(deployment)));
|
|
78
|
-
server.registerTool('deployments_set', {
|
|
79
|
-
description: 'Update deployment labels. Replaces all existing labels.',
|
|
80
|
-
annotations: WRITE,
|
|
81
|
-
inputSchema: {
|
|
82
|
-
deployment: z
|
|
83
|
-
.string()
|
|
84
|
-
.describe('Deployment hostname (e.g. "happy-cat-abc1234.shipstatic.com"). Use deployments_list to find deployments.'),
|
|
85
|
-
labels: z
|
|
86
|
-
.array(z.string())
|
|
87
|
-
.describe('Labels to set. Replaces all existing labels. Pass empty array to clear.'),
|
|
88
|
-
},
|
|
89
|
-
}, ({ deployment, labels }) => call(() => ship.deployments.set(deployment, { labels })));
|
|
90
|
-
server.registerTool('deployments_delete', {
|
|
91
|
-
description: 'Permanently delete a deployment and its files. You MUST confirm with the user before calling this tool, referencing the deployment.',
|
|
92
|
-
annotations: DESTRUCTIVE,
|
|
93
|
-
inputSchema: {
|
|
94
|
-
deployment: z
|
|
95
|
-
.string()
|
|
96
|
-
.describe('Deployment hostname to delete (e.g. "happy-cat-abc1234.shipstatic.com")'),
|
|
97
|
-
},
|
|
98
|
-
}, ({ deployment }) => call(() => ship.deployments.delete(deployment)));
|
|
99
|
-
// Domains
|
|
100
|
-
server.registerTool('domains_set', {
|
|
101
|
-
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.',
|
|
102
|
-
annotations: WRITE,
|
|
103
|
-
inputSchema: {
|
|
104
|
-
domain: z.string().describe('Domain name (e.g. "www.example.com" or "blog.example.com")'),
|
|
105
|
-
deployment: z
|
|
106
|
-
.string()
|
|
107
|
-
.optional()
|
|
108
|
-
.describe('Deployment to serve on this domain (e.g. "happy-cat-abc1234.shipstatic.com"). Omit to reserve the domain without linking.'),
|
|
109
|
-
labels: z
|
|
110
|
-
.array(z.string())
|
|
111
|
-
.optional()
|
|
112
|
-
.describe('Labels for organizing domains (e.g. ["production"]).'),
|
|
113
|
-
},
|
|
114
|
-
}, ({ domain, deployment, labels }) => call(() => ship.domains.set(domain, { deployment, labels })));
|
|
115
|
-
server.registerTool('domains_list', {
|
|
116
|
-
description: 'List all domains with their URLs, linked deployment, and verification status.',
|
|
117
|
-
annotations: READ,
|
|
118
|
-
}, () => call(() => ship.domains.list()));
|
|
119
|
-
server.registerTool('domains_get', {
|
|
120
|
-
description: 'Get domain details including URL, linked deployment, verification status, and labels.',
|
|
121
|
-
annotations: READ,
|
|
122
|
-
inputSchema: {
|
|
123
|
-
domain: z
|
|
124
|
-
.string()
|
|
125
|
-
.describe('Domain name (e.g. "www.example.com"). Use domains_list to find names.'),
|
|
126
|
-
},
|
|
127
|
-
}, ({ domain }) => call(() => ship.domains.get(domain)));
|
|
128
|
-
server.registerTool('domains_records', {
|
|
129
|
-
description: 'Get the DNS records the user needs to configure at their DNS provider. Call after domains_set. You MUST show the returned records to the user.',
|
|
130
|
-
annotations: READ,
|
|
131
|
-
inputSchema: {
|
|
132
|
-
domain: z
|
|
133
|
-
.string()
|
|
134
|
-
.describe('Domain name. Must be a domain previously created with domains_set.'),
|
|
135
|
-
},
|
|
136
|
-
}, ({ domain }) => call(() => ship.domains.records(domain)));
|
|
137
|
-
server.registerTool('domains_dns', {
|
|
138
|
-
description: 'Look up the DNS provider for a domain (e.g. Cloudflare, Namecheap). Helps the user know where to configure their DNS records.',
|
|
139
|
-
annotations: READ,
|
|
140
|
-
inputSchema: {
|
|
141
|
-
domain: z
|
|
142
|
-
.string()
|
|
143
|
-
.describe('Domain name to look up DNS provider for (e.g. "www.example.com")'),
|
|
144
|
-
},
|
|
145
|
-
}, ({ domain }) => call(() => ship.domains.dns(domain)));
|
|
146
|
-
server.registerTool('domains_share', {
|
|
147
|
-
description: 'Get a shareable DNS setup hash for a domain. The hash can be shared with the user so they can view the required DNS records without needing an API key.',
|
|
148
|
-
annotations: READ,
|
|
149
|
-
inputSchema: {
|
|
150
|
-
domain: z
|
|
151
|
-
.string()
|
|
152
|
-
.describe('Domain name to generate a share link for. Must be a domain previously created with domains_set.'),
|
|
153
|
-
},
|
|
154
|
-
}, ({ domain }) => call(() => ship.domains.share(domain)));
|
|
155
|
-
server.registerTool('domains_validate', {
|
|
156
|
-
description: 'Check if a domain name is valid and available before creating it. Returns the normalized form and availability.',
|
|
157
|
-
annotations: READ,
|
|
158
|
-
inputSchema: {
|
|
159
|
-
domain: z
|
|
160
|
-
.string()
|
|
161
|
-
.describe('Domain name to check (e.g. "www.example.com"). Call before domains_set to check availability.'),
|
|
162
|
-
},
|
|
163
|
-
}, ({ domain }) => call(() => ship.domains.validate(domain)));
|
|
164
|
-
server.registerTool('domains_verify', {
|
|
165
|
-
description: 'Trigger DNS verification for a custom domain. Call after the user has configured DNS records from domains_records. Verification is asynchronous — the domain status updates once DNS propagates.',
|
|
166
|
-
annotations: WRITE,
|
|
167
|
-
inputSchema: {
|
|
168
|
-
domain: z
|
|
169
|
-
.string()
|
|
170
|
-
.describe('Domain name to verify DNS for. Must be a domain previously created with domains_set.'),
|
|
171
|
-
},
|
|
172
|
-
}, ({ domain }) => call(() => ship.domains.verify(domain)));
|
|
173
|
-
server.registerTool('domains_delete', {
|
|
174
|
-
description: 'Permanently delete a domain. You MUST confirm with the user before calling this tool, referencing the domain name.',
|
|
175
|
-
annotations: DESTRUCTIVE,
|
|
176
|
-
inputSchema: {
|
|
177
|
-
domain: z.string().describe('Domain name to delete (e.g. "www.example.com")'),
|
|
178
|
-
},
|
|
179
|
-
}, ({ domain }) => call(() => ship.domains.delete(domain)));
|
|
180
|
-
// Debugging
|
|
181
|
-
server.registerTool('whoami', {
|
|
182
|
-
description: 'Show authenticated account details including email, plan, and usage.',
|
|
183
|
-
annotations: READ,
|
|
184
|
-
}, () => call(() => ship.whoami()));
|
|
49
|
+
labels: z.array(z.string()).optional().describe(PARAM_DESCRIPTIONS.labels),
|
|
50
|
+
password: z.string().optional().describe(PARAM_DESCRIPTIONS.password),
|
|
51
|
+
idempotencyKey: z.string().optional().describe(PARAM_DESCRIPTIONS.idempotencyKey),
|
|
52
|
+
},
|
|
53
|
+
}, ({ path, labels, password, idempotencyKey }) => call(() => ship.deployments.upload(path, { labels, password, idempotencyKey, via })));
|
|
54
|
+
// The other fourteen. Identical on every transport, so they live in the
|
|
55
|
+
// shared package rather than here — see tools.ts for why upload is not
|
|
56
|
+
// among them.
|
|
57
|
+
registerAccountTools(server, ship, call);
|
|
185
58
|
return server;
|
|
186
59
|
}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The account-tied toolset — fourteen tools, identical on every transport.
|
|
3
|
+
*
|
|
4
|
+
* `deployments_upload` is not here, and the split is exactly the product's
|
|
5
|
+
* own shape rather than a convenience:
|
|
6
|
+
*
|
|
7
|
+
* - **Upload is the anonymous door.** It is the one operation that works
|
|
8
|
+
* with no account, and it is the one whose INPUT differs by transport —
|
|
9
|
+
* a filesystem path over stdio, inline bytes over HTTP, because a Worker
|
|
10
|
+
* has no filesystem. It also carries the Apps-SDK widget hosted-side.
|
|
11
|
+
* So it is authored per transport, in each `server.ts`.
|
|
12
|
+
* - **Everything else needs an identity**, and once a transport has one,
|
|
13
|
+
* nothing about these fourteen depends on how the bytes arrived. Same
|
|
14
|
+
* names, same schemas, same prose, same 1:1 SDK calls.
|
|
15
|
+
*
|
|
16
|
+
* That is why they live in the shared package: when the hosted transport
|
|
17
|
+
* gains OAuth it registers this function and has the complete toolset, rather
|
|
18
|
+
* than someone copying fourteen definitions into a second repo — which is the
|
|
19
|
+
* moment the two surfaces would begin to drift. The cost of doing it after
|
|
20
|
+
* the copy is a de-duplication under deadline; the cost of doing it before is
|
|
21
|
+
* this file.
|
|
22
|
+
*
|
|
23
|
+
* **The catalogue is static; identity decides what SUCCEEDS.** These are
|
|
24
|
+
* registered whether or not a credential is present — an anonymous caller
|
|
25
|
+
* sees them and gets a typed authentication error naming how to authenticate
|
|
26
|
+
* on *this* transport (the hint is `createCall`'s one per-transport argument).
|
|
27
|
+
* A tool list that changes shape under the caller would be a second, dynamic
|
|
28
|
+
* contract for an agent to track, and MCP clients cache the catalogue.
|
|
29
|
+
*/
|
|
30
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
31
|
+
import type Ship from '@shipstatic/ship';
|
|
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"];
|
|
53
|
+
export declare function registerAccountTools(server: McpServer, ship: Ship, call: CallFn): void;
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The account-tied toolset — fourteen tools, identical on every transport.
|
|
3
|
+
*
|
|
4
|
+
* `deployments_upload` is not here, and the split is exactly the product's
|
|
5
|
+
* own shape rather than a convenience:
|
|
6
|
+
*
|
|
7
|
+
* - **Upload is the anonymous door.** It is the one operation that works
|
|
8
|
+
* with no account, and it is the one whose INPUT differs by transport —
|
|
9
|
+
* a filesystem path over stdio, inline bytes over HTTP, because a Worker
|
|
10
|
+
* has no filesystem. It also carries the Apps-SDK widget hosted-side.
|
|
11
|
+
* So it is authored per transport, in each `server.ts`.
|
|
12
|
+
* - **Everything else needs an identity**, and once a transport has one,
|
|
13
|
+
* nothing about these fourteen depends on how the bytes arrived. Same
|
|
14
|
+
* names, same schemas, same prose, same 1:1 SDK calls.
|
|
15
|
+
*
|
|
16
|
+
* That is why they live in the shared package: when the hosted transport
|
|
17
|
+
* gains OAuth it registers this function and has the complete toolset, rather
|
|
18
|
+
* than someone copying fourteen definitions into a second repo — which is the
|
|
19
|
+
* moment the two surfaces would begin to drift. The cost of doing it after
|
|
20
|
+
* the copy is a de-duplication under deadline; the cost of doing it before is
|
|
21
|
+
* this file.
|
|
22
|
+
*
|
|
23
|
+
* **The catalogue is static; identity decides what SUCCEEDS.** These are
|
|
24
|
+
* registered whether or not a credential is present — an anonymous caller
|
|
25
|
+
* sees them and gets a typed authentication error naming how to authenticate
|
|
26
|
+
* on *this* transport (the hint is `createCall`'s one per-transport argument).
|
|
27
|
+
* A tool list that changes shape under the caller would be a second, dynamic
|
|
28
|
+
* contract for an agent to track, and MCP clients cache the catalogue.
|
|
29
|
+
*/
|
|
30
|
+
import { z } from 'zod';
|
|
31
|
+
import { ANNOTATIONS } from './vocabulary.js';
|
|
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
|
+
];
|
|
68
|
+
/**
|
|
69
|
+
* The pagination surface, shared by every list tool because it is one
|
|
70
|
+
* contract, not two. A list answers `{<collection>, cursor}` and nothing
|
|
71
|
+
* else — `cursor` carries the whole has-more signal and is null on the last
|
|
72
|
+
* page, so there is no `total` to ask for and no has-more boolean.
|
|
73
|
+
*
|
|
74
|
+
* No upper bound is stated here on purpose. The API clamps an unusable
|
|
75
|
+
* `limit` server-side and owns that number; restating a cap in the tool
|
|
76
|
+
* schema would give one fact two owners and let them drift. `min(1)` is not
|
|
77
|
+
* a cap — it rejects a value that could never mean anything.
|
|
78
|
+
*/
|
|
79
|
+
const PAGINATION_INPUT = {
|
|
80
|
+
limit: z
|
|
81
|
+
.number()
|
|
82
|
+
.int()
|
|
83
|
+
.min(1)
|
|
84
|
+
.optional()
|
|
85
|
+
.describe('Maximum number of items to return in one page. Omit for the server default.'),
|
|
86
|
+
cursor: z
|
|
87
|
+
.string()
|
|
88
|
+
.optional()
|
|
89
|
+
.describe("Opaque position from the previous response's `cursor` field; omit for the first page."),
|
|
90
|
+
};
|
|
91
|
+
/** Appended to every list tool's description — the paging contract, stated once. */
|
|
92
|
+
const PAGING_NOTE = " The response's `cursor` is null on the last page; pass it back as `cursor` to fetch the next.";
|
|
93
|
+
/** The deployment argument, described identically wherever it is accepted. */
|
|
94
|
+
const DEPLOYMENT_EXAMPLE = 'happy-cat-abc1234.shipstatic.com';
|
|
95
|
+
export function registerAccountTools(server, ship, call) {
|
|
96
|
+
// Deployments
|
|
97
|
+
server.registerTool('deployments_list', {
|
|
98
|
+
description: `List all deployments with their URLs, status, labels, and password protection state.${PAGING_NOTE}`,
|
|
99
|
+
annotations: READ,
|
|
100
|
+
inputSchema: PAGINATION_INPUT,
|
|
101
|
+
}, ({ limit, cursor }) => call(() => ship.deployments.list({ limit, cursor })));
|
|
102
|
+
server.registerTool('deployments_get', {
|
|
103
|
+
description: 'Get deployment details including URL, status, file count, size, labels, and password protection state.',
|
|
104
|
+
annotations: READ,
|
|
105
|
+
inputSchema: {
|
|
106
|
+
deployment: z
|
|
107
|
+
.string()
|
|
108
|
+
.describe(`Deployment hostname (e.g. "${DEPLOYMENT_EXAMPLE}"). Returned by deployments_upload or deployments_list.`),
|
|
109
|
+
},
|
|
110
|
+
}, ({ deployment }) => call(() => ship.deployments.get(deployment)));
|
|
111
|
+
server.registerTool('deployments_set', {
|
|
112
|
+
description: 'Update deployment labels. Replaces all existing labels.',
|
|
113
|
+
annotations: WRITE,
|
|
114
|
+
inputSchema: {
|
|
115
|
+
deployment: z
|
|
116
|
+
.string()
|
|
117
|
+
.describe(`Deployment hostname (e.g. "${DEPLOYMENT_EXAMPLE}"). Use deployments_list to find deployments.`),
|
|
118
|
+
labels: z
|
|
119
|
+
.array(z.string())
|
|
120
|
+
.describe('Labels to set. Replaces all existing labels. Pass empty array to clear.'),
|
|
121
|
+
},
|
|
122
|
+
}, ({ deployment, labels }) => call(() => ship.deployments.set(deployment, { labels })));
|
|
123
|
+
server.registerTool('deployments_delete', {
|
|
124
|
+
description: 'Permanently delete a deployment and its files. You MUST confirm with the user before calling this tool, referencing the deployment.',
|
|
125
|
+
annotations: DESTRUCTIVE,
|
|
126
|
+
inputSchema: {
|
|
127
|
+
deployment: z
|
|
128
|
+
.string()
|
|
129
|
+
.describe(`Deployment hostname to delete (e.g. "${DEPLOYMENT_EXAMPLE}")`),
|
|
130
|
+
},
|
|
131
|
+
}, ({ deployment }) => call(() => ship.deployments.delete(deployment)));
|
|
132
|
+
// Domains
|
|
133
|
+
server.registerTool('domains_set', {
|
|
134
|
+
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.',
|
|
135
|
+
annotations: WRITE,
|
|
136
|
+
inputSchema: {
|
|
137
|
+
domain: z.string().describe('Domain name (e.g. "www.example.com" or "blog.example.com")'),
|
|
138
|
+
deployment: z
|
|
139
|
+
.string()
|
|
140
|
+
.optional()
|
|
141
|
+
.describe(`Deployment to serve on this domain (e.g. "${DEPLOYMENT_EXAMPLE}"). Omit to reserve the domain without linking.`),
|
|
142
|
+
labels: z
|
|
143
|
+
.array(z.string())
|
|
144
|
+
.optional()
|
|
145
|
+
.describe('Labels for organizing domains (e.g. ["production"]).'),
|
|
146
|
+
},
|
|
147
|
+
}, ({ domain, deployment, labels }) => call(() => ship.domains.set(domain, { deployment, labels })));
|
|
148
|
+
server.registerTool('domains_list', {
|
|
149
|
+
description: `List all domains with their URLs, linked deployment, and verification status.${PAGING_NOTE}`,
|
|
150
|
+
annotations: READ,
|
|
151
|
+
inputSchema: PAGINATION_INPUT,
|
|
152
|
+
}, ({ limit, cursor }) => call(() => ship.domains.list({ limit, cursor })));
|
|
153
|
+
server.registerTool('domains_get', {
|
|
154
|
+
description: 'Get domain details including URL, linked deployment, verification status, and labels.',
|
|
155
|
+
annotations: READ,
|
|
156
|
+
inputSchema: {
|
|
157
|
+
domain: z
|
|
158
|
+
.string()
|
|
159
|
+
.describe('Domain name (e.g. "www.example.com"). Use domains_list to find names.'),
|
|
160
|
+
},
|
|
161
|
+
}, ({ domain }) => call(() => ship.domains.get(domain)));
|
|
162
|
+
server.registerTool('domains_records', {
|
|
163
|
+
description: 'Get the DNS records the user needs to configure at their DNS provider. Call after domains_set. You MUST show the returned records to the user.',
|
|
164
|
+
annotations: READ,
|
|
165
|
+
inputSchema: {
|
|
166
|
+
domain: z
|
|
167
|
+
.string()
|
|
168
|
+
.describe('Domain name. Must be a domain previously created with domains_set.'),
|
|
169
|
+
},
|
|
170
|
+
}, ({ domain }) => call(() => ship.domains.records(domain)));
|
|
171
|
+
server.registerTool('domains_dns', {
|
|
172
|
+
description: 'Look up the DNS provider for a domain (e.g. Cloudflare, Namecheap). Helps the user know where to configure their DNS records.',
|
|
173
|
+
annotations: READ,
|
|
174
|
+
inputSchema: {
|
|
175
|
+
domain: z
|
|
176
|
+
.string()
|
|
177
|
+
.describe('Domain name to look up DNS provider for (e.g. "www.example.com")'),
|
|
178
|
+
},
|
|
179
|
+
}, ({ domain }) => call(() => ship.domains.dns(domain)));
|
|
180
|
+
server.registerTool('domains_share', {
|
|
181
|
+
description: 'Get a shareable DNS setup hash for a domain. The hash can be shared with the user so they can view the required DNS records without needing an API key.',
|
|
182
|
+
annotations: READ,
|
|
183
|
+
inputSchema: {
|
|
184
|
+
domain: z
|
|
185
|
+
.string()
|
|
186
|
+
.describe('Domain name to generate a share link for. Must be a domain previously created with domains_set.'),
|
|
187
|
+
},
|
|
188
|
+
}, ({ domain }) => call(() => ship.domains.share(domain)));
|
|
189
|
+
server.registerTool('domains_validate', {
|
|
190
|
+
description: 'Check if a domain name is valid and available before creating it. Returns the normalized form and availability.',
|
|
191
|
+
annotations: READ,
|
|
192
|
+
inputSchema: {
|
|
193
|
+
domain: z
|
|
194
|
+
.string()
|
|
195
|
+
.describe('Domain name to check (e.g. "www.example.com"). Call before domains_set to check availability.'),
|
|
196
|
+
},
|
|
197
|
+
}, ({ domain }) => call(() => ship.domains.validate(domain)));
|
|
198
|
+
server.registerTool('domains_verify', {
|
|
199
|
+
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.',
|
|
200
|
+
annotations: WRITE,
|
|
201
|
+
inputSchema: {
|
|
202
|
+
domain: z
|
|
203
|
+
.string()
|
|
204
|
+
.describe('Domain name to verify DNS for. Must be a domain previously created with domains_set.'),
|
|
205
|
+
},
|
|
206
|
+
}, ({ domain }) => call(() => ship.domains.verify(domain)));
|
|
207
|
+
server.registerTool('domains_delete', {
|
|
208
|
+
description: 'Permanently delete a domain. You MUST confirm with the user before calling this tool, referencing the domain name.',
|
|
209
|
+
annotations: DESTRUCTIVE,
|
|
210
|
+
inputSchema: {
|
|
211
|
+
domain: z.string().describe('Domain name to delete (e.g. "www.example.com")'),
|
|
212
|
+
},
|
|
213
|
+
}, ({ domain }) => call(() => ship.domains.delete(domain)));
|
|
214
|
+
// Account
|
|
215
|
+
server.registerTool('whoami', {
|
|
216
|
+
description: 'Show authenticated account details including email, plan, and usage.',
|
|
217
|
+
annotations: READ,
|
|
218
|
+
}, () => call(() => ship.whoami()));
|
|
219
|
+
}
|