@drop2run/mcp 0.4.3 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/dist/index.js +52 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,6 +71,12 @@ lose it.
|
|
|
71
71
|
Both publish tools take an optional `site` — a subdomain or site id to publish over. Leave it out and a
|
|
72
72
|
new site is created.
|
|
73
73
|
|
|
74
|
+
Both also take an optional `subdomain`, which names the new site instead of letting the server generate
|
|
75
|
+
one. It is for a person who asked for a particular address: the name becomes the URL, cannot be changed
|
|
76
|
+
afterwards, and a site made under a guessed one has to be deleted by hand. `site` and `subdomain` are
|
|
77
|
+
opposites — one publishes over a site that exists, the other creates one that does not — so passing both
|
|
78
|
+
is refused rather than resolved.
|
|
79
|
+
|
|
74
80
|
Both sign-in tools answer "not approved yet" rather than failing while they wait, and calling them
|
|
75
81
|
again keeps waiting on the same sign-in. Neither replaces a token that is already stored unless asked
|
|
76
82
|
to with `replace`.
|
|
@@ -78,7 +84,9 @@ to with `replace`.
|
|
|
78
84
|
## Behaviour worth knowing
|
|
79
85
|
|
|
80
86
|
**Publishing without a `site` creates a new one.** It never replaces your most recent site by default:
|
|
81
|
-
"put this online" is not "and overwrite what I published last time".
|
|
87
|
+
"put this online" is not "and overwrite what I published last time". Say what address you want and pass
|
|
88
|
+
it as `subdomain`; say nothing and the name is generated, which is the right default for something
|
|
89
|
+
nobody is going to link to.
|
|
82
90
|
|
|
83
91
|
**A site can be documents instead of a built site.** A publish needs an `index.html` at the top level,
|
|
84
92
|
or at least one `.md`, `.markdown` or `.pdf` file — those are served through the reader. So a single
|
package/dist/index.js
CHANGED
|
@@ -47,11 +47,17 @@ async function listSites(credentials) {
|
|
|
47
47
|
const body = await call(credentials, "sites");
|
|
48
48
|
return body.sites;
|
|
49
49
|
}
|
|
50
|
-
async function createSite(credentials) {
|
|
50
|
+
async function createSite(credentials, subdomain) {
|
|
51
51
|
const created = await call(
|
|
52
52
|
credentials,
|
|
53
53
|
"sites",
|
|
54
|
-
{
|
|
54
|
+
{
|
|
55
|
+
method: "POST",
|
|
56
|
+
// An absent field rather than a null one, because the API reads null, empty and missing the
|
|
57
|
+
// same way and a body carrying only what was asked for is the one that reads correctly in a
|
|
58
|
+
// log.
|
|
59
|
+
body: JSON.stringify(subdomain === void 0 ? {} : { subdomain })
|
|
60
|
+
}
|
|
55
61
|
);
|
|
56
62
|
return { ...created, name: null };
|
|
57
63
|
}
|
|
@@ -304,6 +310,7 @@ const ClientErrorCode = {
|
|
|
304
310
|
/** An upload kept failing after every retry. */
|
|
305
311
|
UploadFailed: "upload_failed"
|
|
306
312
|
};
|
|
313
|
+
const ACCOUNT_HEADER = "X-Drop2Run-Account";
|
|
307
314
|
const CLAIM_TOKEN_HEADER = "X-Claim-Token";
|
|
308
315
|
async function prepareDeploy(siteId, files, options, signal) {
|
|
309
316
|
const manifest = files.map((file) => ({
|
|
@@ -340,6 +347,7 @@ async function request(path, body, options, signal) {
|
|
|
340
347
|
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
341
348
|
if (options.claimToken) headers[CLAIM_TOKEN_HEADER] = options.claimToken;
|
|
342
349
|
if (options.token) headers.Authorization = `Bearer ${options.token}`;
|
|
350
|
+
if (options.accountId) headers[ACCOUNT_HEADER] = options.accountId;
|
|
343
351
|
const response = await doFetch(`${base}/${path}`, {
|
|
344
352
|
method: "POST",
|
|
345
353
|
headers,
|
|
@@ -633,7 +641,7 @@ async function uploadAll(targets, uploadUrl, files, onProgress, signal, deps = {
|
|
|
633
641
|
});
|
|
634
642
|
done += 1;
|
|
635
643
|
uploadedBytes += file.bytes.length;
|
|
636
|
-
onProgress?.(done, targets.length, uploadedBytes);
|
|
644
|
+
onProgress?.(done, targets.length, uploadedBytes, file.path);
|
|
637
645
|
}
|
|
638
646
|
};
|
|
639
647
|
const workers = Array.from(
|
|
@@ -728,12 +736,13 @@ async function deploy(source, siteId, onProgress, signal, options = {}) {
|
|
|
728
736
|
prepared.upload,
|
|
729
737
|
prepared.uploadUrl,
|
|
730
738
|
hashed,
|
|
731
|
-
(done, _total, bytes) => onProgress({
|
|
739
|
+
(done, _total, bytes, path) => onProgress({
|
|
732
740
|
type: "uploading",
|
|
733
741
|
done: prepared.reused + done,
|
|
734
742
|
total: prepared.total,
|
|
735
743
|
bytes,
|
|
736
|
-
reused: prepared.reused
|
|
744
|
+
reused: prepared.reused,
|
|
745
|
+
path
|
|
737
746
|
}),
|
|
738
747
|
signal,
|
|
739
748
|
options.upload
|
|
@@ -799,7 +808,15 @@ function escapesRoot(root, candidate) {
|
|
|
799
808
|
const inside = relative(root, candidate);
|
|
800
809
|
return inside === "" || inside.startsWith("..");
|
|
801
810
|
}
|
|
802
|
-
async function resolveSite(credentials, site) {
|
|
811
|
+
async function resolveSite(credentials, site, subdomain) {
|
|
812
|
+
if (subdomain !== void 0) {
|
|
813
|
+
if (site !== void 0) {
|
|
814
|
+
throw new Error(
|
|
815
|
+
"Give either an existing site or a subdomain for a new one, not both: publishing over a site and creating one are different requests."
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
return await createSite(credentials, subdomain);
|
|
819
|
+
}
|
|
803
820
|
if (site === void 0) return await createSite(credentials);
|
|
804
821
|
const wanted = site.trim().toLowerCase();
|
|
805
822
|
const found = (await listSites(credentials)).find(
|
|
@@ -807,13 +824,13 @@ async function resolveSite(credentials, site) {
|
|
|
807
824
|
);
|
|
808
825
|
if (found === void 0) {
|
|
809
826
|
throw new Error(
|
|
810
|
-
`No site of yours is called "${site}". Leave the site out to publish to a new one, or
|
|
827
|
+
`No site of yours is called "${site}". Leave the site out to publish to a new one, or list your sites to see what exists.`
|
|
811
828
|
);
|
|
812
829
|
}
|
|
813
830
|
return found;
|
|
814
831
|
}
|
|
815
|
-
async function publish(credentials, source, site) {
|
|
816
|
-
const target = await resolveSite(credentials, site);
|
|
832
|
+
async function publish(credentials, source, site, onProgress, subdomain) {
|
|
833
|
+
const target = await resolveSite(credentials, site, subdomain);
|
|
817
834
|
let files = 0;
|
|
818
835
|
let url = target.url;
|
|
819
836
|
let unchanged = false;
|
|
@@ -843,8 +860,11 @@ async function publish(credentials, source, site) {
|
|
|
843
860
|
});
|
|
844
861
|
return { url, siteId: target.siteId, subdomain: target.subdomain, files, unchanged };
|
|
845
862
|
}
|
|
846
|
-
function publishDirectory(credentials, directory, site) {
|
|
847
|
-
|
|
863
|
+
async function publishDirectory(credentials, directory, site, onProgress, subdomain) {
|
|
864
|
+
const found = await stat(directory).catch(() => null);
|
|
865
|
+
if (found === null) throw new Error(`There is no folder at ${directory}.`);
|
|
866
|
+
if (!found.isDirectory()) throw new Error(`${directory} is a file, not a folder to publish.`);
|
|
867
|
+
return await publish(credentials, directorySource(directory), site, onProgress, subdomain);
|
|
848
868
|
}
|
|
849
869
|
function cleanPath(path) {
|
|
850
870
|
const cleaned = path.trim().replaceAll("\\", "/").replace(/^\.?\//, "");
|
|
@@ -858,7 +878,7 @@ function cleanPath(path) {
|
|
|
858
878
|
}
|
|
859
879
|
return cleaned;
|
|
860
880
|
}
|
|
861
|
-
async function publishFiles(credentials, files, site) {
|
|
881
|
+
async function publishFiles(credentials, files, site, subdomain) {
|
|
862
882
|
const encoder = new TextEncoder();
|
|
863
883
|
const collected = files.map((file) => ({
|
|
864
884
|
path: cleanPath(file.path),
|
|
@@ -869,7 +889,7 @@ async function publishFiles(credentials, files, site) {
|
|
|
869
889
|
if (seen.has(file.path)) throw new Error(`Two files were given the same path: ${file.path}`);
|
|
870
890
|
seen.add(file.path);
|
|
871
891
|
}
|
|
872
|
-
return publish(credentials, async () => collected, site);
|
|
892
|
+
return publish(credentials, async () => collected, site, void 0, subdomain);
|
|
873
893
|
}
|
|
874
894
|
const DEFAULT_WAIT_SECONDS = 120;
|
|
875
895
|
const MAX_WAIT_SECONDS = 240;
|
|
@@ -1047,7 +1067,7 @@ async function signInWithCode(waitSeconds, replace = false) {
|
|
|
1047
1067
|
"Call this tool again to keep waiting. Nothing has been changed yet."
|
|
1048
1068
|
].join("\n");
|
|
1049
1069
|
}
|
|
1050
|
-
const SERVER_VERSION = "0.
|
|
1070
|
+
const SERVER_VERSION = "0.5.1";
|
|
1051
1071
|
const CREDENTIAL_WRITE = {
|
|
1052
1072
|
readOnlyHint: false,
|
|
1053
1073
|
destructiveHint: false,
|
|
@@ -1126,6 +1146,12 @@ function createServer() {
|
|
|
1126
1146
|
"publish to a brand-new site; pass a subdomain to publish over an existing one. list_sites",
|
|
1127
1147
|
"shows what already exists.",
|
|
1128
1148
|
"",
|
|
1149
|
+
"A new site gets a generated name unless `subdomain` asks for one. Pass it when the person",
|
|
1150
|
+
"named the address they want, and otherwise leave it out — a name is what somebody will be",
|
|
1151
|
+
"given as a URL, it cannot be changed later, and a site created under a guessed one has to",
|
|
1152
|
+
"be deleted by hand. `site` and `subdomain` are opposites: one publishes over a site that",
|
|
1153
|
+
"exists, the other creates one that does not. Never pass both.",
|
|
1154
|
+
"",
|
|
1129
1155
|
"A site needs an index.html at its top level, or at least one .md, .markdown or .pdf file,",
|
|
1130
1156
|
"which publishes as a documents site and is read through a viewer. So one page goes at",
|
|
1131
1157
|
"index.html, and a single note is a whole publish that needs no wrapping in HTML.",
|
|
@@ -1183,13 +1209,16 @@ function createServer() {
|
|
|
1183
1209
|
content: z.string().describe("The file's contents.")
|
|
1184
1210
|
})
|
|
1185
1211
|
).min(1).describe("The files to publish."),
|
|
1186
|
-
site: z.string().optional().describe("Subdomain or site id to publish over. Omit to create a new site.")
|
|
1212
|
+
site: z.string().optional().describe("Subdomain or site id to publish over. Omit to create a new site."),
|
|
1213
|
+
subdomain: z.string().optional().describe(
|
|
1214
|
+
"Subdomain to create the new site under, when the person asked for a particular address. Only for a new site: leave it out to publish over an existing one, and do not pass it together with `site`. Ask rather than inventing one — the name is the URL somebody will be given, it cannot be changed afterwards, and an unused site made under a guessed name has to be deleted by hand."
|
|
1215
|
+
)
|
|
1187
1216
|
},
|
|
1188
1217
|
outputSchema: PUBLISH_OUTPUT,
|
|
1189
1218
|
annotations: REPLACES_A_SITE
|
|
1190
1219
|
},
|
|
1191
|
-
({ files, site }) => withCredentials(async (credentials) => {
|
|
1192
|
-
const result = await publishFiles(credentials, files, site);
|
|
1220
|
+
({ files, site, subdomain }) => withCredentials(async (credentials) => {
|
|
1221
|
+
const result = await publishFiles(credentials, files, site, subdomain);
|
|
1193
1222
|
return report(describe(result), { ...result });
|
|
1194
1223
|
})
|
|
1195
1224
|
);
|
|
@@ -1200,13 +1229,16 @@ function createServer() {
|
|
|
1200
1229
|
description: "Publishes a folder of static files and returns its URL. The folder needs an index.html at its root, or .md and .pdf files, which are served through the reader.",
|
|
1201
1230
|
inputSchema: {
|
|
1202
1231
|
path: z.string().min(1).describe("Absolute path of the folder to publish."),
|
|
1203
|
-
site: z.string().optional().describe("Subdomain or site id to publish over. Omit to create a new site.")
|
|
1232
|
+
site: z.string().optional().describe("Subdomain or site id to publish over. Omit to create a new site."),
|
|
1233
|
+
subdomain: z.string().optional().describe(
|
|
1234
|
+
"Subdomain to create the new site under, when the person asked for a particular address. Only for a new site: leave it out to publish over an existing one, and do not pass it together with `site`. Ask rather than inventing one — the name is the URL somebody will be given, it cannot be changed afterwards, and an unused site made under a guessed name has to be deleted by hand."
|
|
1235
|
+
)
|
|
1204
1236
|
},
|
|
1205
1237
|
outputSchema: PUBLISH_OUTPUT,
|
|
1206
1238
|
annotations: REPLACES_A_SITE
|
|
1207
1239
|
},
|
|
1208
|
-
({ path, site }) => withCredentials(async (credentials) => {
|
|
1209
|
-
const result = await publishDirectory(credentials, path, site);
|
|
1240
|
+
({ path, site, subdomain }) => withCredentials(async (credentials) => {
|
|
1241
|
+
const result = await publishDirectory(credentials, path, site, void 0, subdomain);
|
|
1210
1242
|
return report(describe(result), { ...result });
|
|
1211
1243
|
})
|
|
1212
1244
|
);
|