@capacms/mcp 0.2.0 → 0.3.0
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 +250 -42
- package/bin/capa-mcp.mjs +18 -60
- package/lib/annotations.mjs +4 -4
- package/lib/arguments.mjs +2 -1
- package/lib/client.mjs +104 -22
- package/lib/connect.mjs +67 -0
- package/lib/entry-tools.mjs +425 -0
- package/lib/error-guide.mjs +1 -1
- package/lib/explore.mjs +19 -4
- package/lib/graphql/schema.mjs +6 -2
- package/lib/graphql/served.mjs +11 -4
- package/lib/graphql-tools.mjs +37 -4
- package/lib/index.mjs +27 -0
- package/lib/instructions.mjs +2 -0
- package/lib/media-tools.mjs +311 -0
- package/lib/registry.mjs +201 -33
- package/lib/rest-tools.mjs +17 -3
- package/lib/server.mjs +39 -22
- package/lib/stdio.mjs +47 -0
- package/lib/tools.mjs +210 -49
- package/lib/uploader-retry.mjs +82 -0
- package/package.json +7 -3
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* uploader-retry.mjs — send to Capa's uploader, and wait out a busy one.
|
|
3
|
+
*
|
|
4
|
+
* The uploader runs a few uploads at once per machine and lets one project,
|
|
5
|
+
* or one person, hold half of them (apps/uploader/app.js). Past that it
|
|
6
|
+
* answers 503 with `Retry-After` and a `reason`: uploader_busy, project_busy
|
|
7
|
+
* or account_busy. Busy is not a failure: the same request a few seconds
|
|
8
|
+
* later goes through. `sendToUploader` waits `Retry-After` (at most 10 s, up
|
|
9
|
+
* to a quarter more for jitter, never past 10 s) and sends again, up to 4
|
|
10
|
+
* times. Every other answer comes back as it is, 408 too_slow and 413
|
|
11
|
+
* too_large included: the same file sent the same way gets the same answer.
|
|
12
|
+
*
|
|
13
|
+
* capa_upload_media (media-tools.mjs) sends through this, and so does
|
|
14
|
+
* `capa media upload`, which runs that tool. The admin follows the same rule
|
|
15
|
+
* (apps/admin/src/lib/external-apis/upload-queue.ts); keep the reasons and
|
|
16
|
+
* the numbers the same in both.
|
|
17
|
+
*
|
|
18
|
+
* `send` makes ONE attempt and answers a fetch Response. It is called again
|
|
19
|
+
* for each retry, so it builds its body and its timeout signal each time: a
|
|
20
|
+
* FormData over a file Blob (`fs.openAsBlob`) reads the file again.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** The `reason`s of the uploader's busy 503s. Any other 503 is answered as it is. */
|
|
24
|
+
export const BUSY_REASONS = new Set(["uploader_busy", "project_busy", "account_busy"]);
|
|
25
|
+
|
|
26
|
+
/** How many times one upload is sent again after a busy answer. */
|
|
27
|
+
export const BUSY_RETRIES = 4;
|
|
28
|
+
|
|
29
|
+
/** The longest one wait lasts, whatever `Retry-After` says. */
|
|
30
|
+
export const MAX_BUSY_WAIT_MS = 10_000;
|
|
31
|
+
|
|
32
|
+
/** The wait when a busy answer names none. The uploader sends 5. */
|
|
33
|
+
const DEFAULT_BUSY_WAIT_SECONDS = 5;
|
|
34
|
+
|
|
35
|
+
function seconds(value) {
|
|
36
|
+
if (value === undefined || value === null || value === "") return null;
|
|
37
|
+
const n = Number(value);
|
|
38
|
+
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* How long to wait before sending again, or null when the answer is not a
|
|
43
|
+
* busy one. `retryAfter` is the header's value; the body's `retryAfter`
|
|
44
|
+
* stands in when there is none.
|
|
45
|
+
*/
|
|
46
|
+
export function busyWaitMs(status, body, retryAfter, random = Math.random) {
|
|
47
|
+
if (status !== 503 || !body || typeof body !== "object" || !BUSY_REASONS.has(body.reason)) return null;
|
|
48
|
+
const wait = seconds(retryAfter) ?? seconds(body.retryAfter) ?? DEFAULT_BUSY_WAIT_SECONDS;
|
|
49
|
+
return Math.round(Math.min(MAX_BUSY_WAIT_MS, wait * 1000 * (1 + 0.25 * random())));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const pause = (ms, signal) =>
|
|
53
|
+
new Promise((resolve) => {
|
|
54
|
+
if (signal?.aborted) return resolve();
|
|
55
|
+
const timer = setTimeout(resolve, ms);
|
|
56
|
+
signal?.addEventListener("abort", () => {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
resolve();
|
|
59
|
+
}, { once: true });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Send with `send`, waiting out busy answers. Answers the last attempt as
|
|
64
|
+
* `{ res, text, body, retries }`: its Response, its body as text and parsed
|
|
65
|
+
* (null when not JSON), and how many times it was sent again.
|
|
66
|
+
*/
|
|
67
|
+
export async function sendToUploader(send, { retries = BUSY_RETRIES, random = Math.random, sleep = pause, signal } = {}) {
|
|
68
|
+
for (let attempt = 0; ; attempt++) {
|
|
69
|
+
const res = await send();
|
|
70
|
+
const text = await res.text();
|
|
71
|
+
let body = null;
|
|
72
|
+
try {
|
|
73
|
+
body = JSON.parse(text);
|
|
74
|
+
} catch {
|
|
75
|
+
body = null;
|
|
76
|
+
}
|
|
77
|
+
const wait = attempt < retries ? busyWaitMs(res.status, body, res.headers.get("retry-after"), random) : null;
|
|
78
|
+
if (wait === null || signal?.aborted) return { res, text, body, retries: attempt };
|
|
79
|
+
await sleep(wait, signal);
|
|
80
|
+
if (signal?.aborted) return { res, text, body, retries: attempt };
|
|
81
|
+
}
|
|
82
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capacms/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "stdio MCP server for Capa, shaped for agents rather than one tool per endpoint.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
|
-
"homepage": "https://docs.capacms.com/
|
|
6
|
+
"homepage": "https://docs.capacms.com/ai/mcp",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"bin": {
|
|
9
9
|
"capa-mcp": "bin/capa-mcp.mjs"
|
|
10
10
|
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./lib/index.mjs",
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
11
15
|
"files": [
|
|
12
16
|
"bin",
|
|
13
17
|
"lib"
|
|
@@ -19,6 +23,6 @@
|
|
|
19
23
|
"node": ">=20.3"
|
|
20
24
|
},
|
|
21
25
|
"scripts": {
|
|
22
|
-
"test": "node --test test/comments.test.mjs test/session.test.mjs test/server.test.mjs test/bound.test.mjs test/document.test.mjs test/graphql-tools.test.mjs test/graphql-contract.test.mjs test/package.test.mjs"
|
|
26
|
+
"test": "node --test test/comments.test.mjs test/session.test.mjs test/server.test.mjs test/bound.test.mjs test/document.test.mjs test/graphql-tools.test.mjs test/graphql-contract.test.mjs test/package.test.mjs test/index.test.mjs test/entry-tools.test.mjs test/tool-contract.test.mjs test/media-tools.test.mjs test/uploader-retry.test.mjs test/hosted.test.mjs"
|
|
23
27
|
}
|
|
24
28
|
}
|