@caelo-cms/mcp-server 0.10.55 → 0.10.58
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/LICENSE +373 -0
- package/README.md +7 -0
- package/dist/admin-server.d.ts +1 -15
- package/dist/admin-server.d.ts.map +1 -1
- package/dist/admin-server.js +72 -302
- package/dist/admin-server.js.map +1 -1
- package/dist/admin.js +7 -8
- package/dist/admin.js.map +1 -1
- package/dist/chat-bridge.d.ts +1 -1
- package/dist/chat-bridge.d.ts.map +1 -1
- package/dist/image-upload.d.ts +2 -75
- package/dist/image-upload.d.ts.map +1 -1
- package/dist/image-upload.js +40 -134
- package/dist/image-upload.js.map +1 -1
- package/dist/index.js +38 -30
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +0 -6
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +24 -259
- package/dist/server.js.map +1 -1
- package/dist/surfaces/admin-surface.d.ts +106 -0
- package/dist/surfaces/admin-surface.d.ts.map +1 -0
- package/dist/surfaces/admin-surface.js +183 -0
- package/dist/surfaces/admin-surface.js.map +1 -0
- package/dist/surfaces/chat-surface.d.ts +48 -0
- package/dist/surfaces/chat-surface.d.ts.map +1 -0
- package/dist/surfaces/chat-surface.js +190 -0
- package/dist/surfaces/chat-surface.js.map +1 -0
- package/dist/surfaces/index.d.ts +15 -0
- package/dist/surfaces/index.d.ts.map +1 -0
- package/dist/surfaces/index.js +15 -0
- package/dist/surfaces/index.js.map +1 -0
- package/dist/surfaces/upload.d.ts +32 -0
- package/dist/surfaces/upload.d.ts.map +1 -0
- package/dist/surfaces/upload.js +58 -0
- package/dist/surfaces/upload.js.map +1 -0
- package/dist/tool-definitions.d.ts +465 -0
- package/dist/tool-definitions.d.ts.map +1 -0
- package/dist/tool-definitions.js +256 -0
- package/dist/tool-definitions.js.map +1 -0
- package/dist/unconfigured.d.ts +26 -0
- package/dist/unconfigured.d.ts.map +1 -0
- package/dist/unconfigured.js +82 -0
- package/dist/unconfigured.js.map +1 -0
- package/package.json +10 -1
- package/power-catalogue.json +8116 -0
package/dist/image-upload.js
CHANGED
|
@@ -1,121 +1,47 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* caelo_upload_images for the stdio shims: reads local files (or base64)
|
|
4
|
+
* and POSTs each image to the admin's `/api/mcp/images`. The per-file loop
|
|
5
|
+
* and the tool definition are shared with the admin's remote endpoint
|
|
6
|
+
* (./surfaces/upload.ts, ./tool-definitions.ts).
|
|
7
|
+
*/
|
|
3
8
|
import { constants } from "node:fs";
|
|
4
9
|
import { open } from "node:fs/promises";
|
|
5
|
-
import { basename } from "node:path";
|
|
6
|
-
import { z } from "zod";
|
|
7
10
|
import { resolveTimeoutMs } from "./http.js";
|
|
8
11
|
import { describeIapRejection, ingressHeaders } from "./ingress-auth.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
/** A bounded batch; each file has its own success/error result for safe retries. */
|
|
34
|
-
export const uploadImagesInput = z.object({ images: z.array(imageInput).min(1).max(4) }).strict();
|
|
35
|
-
/** Exposed in both MCP catalogues, including chat-scoped connections. */
|
|
36
|
-
export const UPLOAD_IMAGES_TOOL = {
|
|
37
|
-
name: "caelo_upload_images",
|
|
38
|
-
description: "Upload 1–4 PNG, JPEG, WebP or GIF images (5 MiB each) to the Caelo media library. " +
|
|
39
|
-
"Prefer filePath for files on the machine running this MCP server; otherwise supply raw base64 (no data URL). " +
|
|
40
|
-
"Files become shared CMS media assets. Set reference: true for images that only guide image generation or editing " +
|
|
41
|
-
"(character sheets, style samples): they stay out of the page-image library and are never published. " +
|
|
42
|
-
"Returns attachments for caelo_chat " +
|
|
43
|
-
"or asset IDs for page tools. Uploading does not send a chat message or publish a page. " +
|
|
44
|
-
"Results are per file: retry only failures. Requires the token owner's current content.write permission.",
|
|
45
|
-
inputSchema: {
|
|
46
|
-
type: "object",
|
|
47
|
-
required: ["images"],
|
|
48
|
-
additionalProperties: false,
|
|
49
|
-
properties: {
|
|
50
|
-
images: {
|
|
51
|
-
type: "array",
|
|
52
|
-
minItems: 1,
|
|
53
|
-
maxItems: 4,
|
|
54
|
-
items: {
|
|
55
|
-
type: "object",
|
|
56
|
-
additionalProperties: false,
|
|
57
|
-
properties: {
|
|
58
|
-
filePath: {
|
|
59
|
-
type: "string",
|
|
60
|
-
description: "Path on the MCP server machine. Mutually exclusive with base64.",
|
|
61
|
-
},
|
|
62
|
-
base64: {
|
|
63
|
-
type: "string",
|
|
64
|
-
description: "Raw image bytes as base64. Mutually exclusive with filePath.",
|
|
65
|
-
},
|
|
66
|
-
filename: { type: "string", description: "Optional media filename." },
|
|
67
|
-
alt: { type: "string", description: "Optional description of the image." },
|
|
68
|
-
reference: {
|
|
69
|
-
type: "boolean",
|
|
70
|
-
description: "True for an image that only guides generation/editing; it is never placed on a page.",
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
oneOf: [{ required: ["filePath"] }, { required: ["base64"] }],
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
};
|
|
12
|
+
import { uploadImagesWith } from "./surfaces/upload.js";
|
|
13
|
+
import { MAX_IMAGE_BYTES } from "./tool-definitions.js";
|
|
14
|
+
/** Read a local image file, refusing anything that is not a 1 B – 5 MiB regular file. */
|
|
15
|
+
async function readLocalImage(filePath) {
|
|
16
|
+
const file = await open(filePath, constants.O_RDONLY | constants.O_NONBLOCK);
|
|
17
|
+
try {
|
|
18
|
+
const stat = await file.stat();
|
|
19
|
+
if (!stat.isFile() || stat.size === 0 || stat.size > MAX_IMAGE_BYTES)
|
|
20
|
+
throw new Error("Expected an image file of 1 byte to 5 MiB");
|
|
21
|
+
// Read at most cap+1 even if the file grows after stat().
|
|
22
|
+
const buffer = Buffer.alloc(MAX_IMAGE_BYTES + 1);
|
|
23
|
+
let total = 0;
|
|
24
|
+
while (total < buffer.length) {
|
|
25
|
+
const { bytesRead } = await file.read(buffer, total, buffer.length - total, null);
|
|
26
|
+
if (!bytesRead)
|
|
27
|
+
break;
|
|
28
|
+
total += bytesRead;
|
|
29
|
+
}
|
|
30
|
+
return buffer.subarray(0, total);
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
await file.close();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
79
36
|
/** Upload caller-selected files, returning failures individually without dropping successes. */
|
|
80
37
|
export async function uploadImages(opts, input) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
const stat = await file.stat();
|
|
90
|
-
if (!stat.isFile() || stat.size === 0 || stat.size > MAX_BYTES)
|
|
91
|
-
throw new Error("Expected an image file of 1 byte to 5 MiB");
|
|
92
|
-
// Read at most cap+1 even if the file grows after stat().
|
|
93
|
-
const buffer = Buffer.alloc(MAX_BYTES + 1);
|
|
94
|
-
let total = 0;
|
|
95
|
-
while (total < buffer.length) {
|
|
96
|
-
const { bytesRead } = await file.read(buffer, total, buffer.length - total, null);
|
|
97
|
-
if (!bytesRead)
|
|
98
|
-
break;
|
|
99
|
-
total += bytesRead;
|
|
100
|
-
}
|
|
101
|
-
bytes = buffer.subarray(0, total);
|
|
102
|
-
}
|
|
103
|
-
finally {
|
|
104
|
-
await file.close();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
if (image.base64 === undefined)
|
|
109
|
-
throw new Error("Missing image bytes");
|
|
110
|
-
bytes = Buffer.from(image.base64, "base64");
|
|
111
|
-
}
|
|
112
|
-
if (!bytes.length || bytes.length > MAX_BYTES)
|
|
113
|
-
throw new Error("Image must be at most 5 MiB and non-empty");
|
|
114
|
-
const filename = image.filename ?? (image.filePath ? basename(image.filePath) : "image");
|
|
115
|
-
const query = new URLSearchParams({ filename });
|
|
116
|
-
if (image.alt)
|
|
117
|
-
query.set("alt", image.alt);
|
|
118
|
-
if (image.reference)
|
|
38
|
+
return uploadImagesWith(input, {
|
|
39
|
+
readFile: readLocalImage,
|
|
40
|
+
store: async (bytes, meta) => {
|
|
41
|
+
const query = new URLSearchParams({ filename: meta.filename });
|
|
42
|
+
if (meta.alt)
|
|
43
|
+
query.set("alt", meta.alt);
|
|
44
|
+
if (meta.reference)
|
|
119
45
|
query.set("visibility", "reference");
|
|
120
46
|
const response = await fetch(`${opts.adminUrl.replace(/\/+$/, "")}/api/mcp/images?${query}`, {
|
|
121
47
|
method: "POST",
|
|
@@ -132,28 +58,8 @@ export async function uploadImages(opts, input) {
|
|
|
132
58
|
throw new Error(describeIapRejection(response, text) ?? `HTTP ${response.status}: ${text.slice(0, 500)}`);
|
|
133
59
|
}
|
|
134
60
|
const value = (await response.json());
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
alt: image.alt ?? filename,
|
|
139
|
-
});
|
|
140
|
-
results.push({ index, attachment });
|
|
141
|
-
}
|
|
142
|
-
catch (e) {
|
|
143
|
-
results.push({ index, error: e instanceof Error ? e.message : String(e) });
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return {
|
|
147
|
-
...(results.every((r) => "error" in r) ? { isError: true } : {}),
|
|
148
|
-
content: [
|
|
149
|
-
{
|
|
150
|
-
type: "text",
|
|
151
|
-
text: JSON.stringify({
|
|
152
|
-
results,
|
|
153
|
-
attachments: results.flatMap((r) => ("attachment" in r ? [r.attachment] : [])),
|
|
154
|
-
}),
|
|
155
|
-
},
|
|
156
|
-
],
|
|
157
|
-
};
|
|
61
|
+
return { assetId: value.assetId, mime: value.mime };
|
|
62
|
+
},
|
|
63
|
+
});
|
|
158
64
|
}
|
|
159
65
|
//# sourceMappingURL=image-upload.js.map
|
package/dist/image-upload.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-upload.js","sourceRoot":"","sources":["../src/image-upload.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC
|
|
1
|
+
{"version":3,"file":"image-upload.js","sourceRoot":"","sources":["../src/image-upload.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -22,45 +22,53 @@
|
|
|
22
22
|
*
|
|
23
23
|
* Every mode first matches its release to the admin's (#764, see
|
|
24
24
|
* ./version-handshake.ts), so the `claude mcp add` command carries no
|
|
25
|
-
* version and survives admin upgrades.
|
|
25
|
+
* version and survives admin upgrades. Without credentials the MCP modes
|
|
26
|
+
* start unconfigured instead (#841, see ./unconfigured.ts).
|
|
26
27
|
*/
|
|
27
28
|
import { startAdminMcpServer } from "./admin-server.js";
|
|
28
29
|
import { runExport } from "./export.js";
|
|
29
30
|
import { startMcpServer } from "./server.js";
|
|
31
|
+
import { missingCredentials, startUnconfiguredServer } from "./unconfigured.js";
|
|
30
32
|
import { ensureAdminRelease } from "./version-handshake.js";
|
|
31
|
-
const adminUrl = process.env.CAELO_ADMIN_URL;
|
|
32
|
-
const token = process.env.CAELO_MCP_TOKEN;
|
|
33
|
-
if (!adminUrl) {
|
|
34
|
-
console.error("CAELO_ADMIN_URL not set — point me at your admin install (e.g. https://admin.example.com)");
|
|
35
|
-
process.exit(2);
|
|
36
|
-
}
|
|
37
|
-
if (!token) {
|
|
38
|
-
console.error("CAELO_MCP_TOKEN not set — mint one at /settings/mcp on your admin install");
|
|
39
|
-
process.exit(2);
|
|
40
|
-
}
|
|
41
|
-
// #764 — run as the admin's own release (re-execs on a mismatch).
|
|
42
|
-
await ensureAdminRelease({ adminUrl, token, bin: "caelo-mcp-server" });
|
|
43
33
|
const command = process.argv[2];
|
|
44
|
-
if (command
|
|
45
|
-
const outFlag = process.argv.indexOf("--out");
|
|
46
|
-
const outDir = outFlag !== -1 ? process.argv[outFlag + 1] : undefined;
|
|
47
|
-
if (outFlag !== -1 && !outDir) {
|
|
48
|
-
console.error("--out needs a directory argument");
|
|
49
|
-
process.exit(2);
|
|
50
|
-
}
|
|
51
|
-
const written = await runExport({ adminUrl, token, outDir: outDir ?? process.cwd() });
|
|
52
|
-
for (const path of written)
|
|
53
|
-
console.log(path);
|
|
54
|
-
console.log(`exported ${written.length} file(s) — re-run after skills or site memory change`);
|
|
55
|
-
}
|
|
56
|
-
else if (command === "admin") {
|
|
57
|
-
await startAdminMcpServer({ adminUrl, token });
|
|
58
|
-
}
|
|
59
|
-
else if (command !== undefined) {
|
|
34
|
+
if (command !== undefined && command !== "admin" && command !== "export") {
|
|
60
35
|
console.error(`unknown command: ${command} (expected no command, "admin", or "export")`);
|
|
61
36
|
process.exit(2);
|
|
62
37
|
}
|
|
38
|
+
// #841 — the MCP modes start without credentials so directories can list
|
|
39
|
+
// their tools; `export` is a one-shot CLI with nothing to list, so it
|
|
40
|
+
// still exits.
|
|
41
|
+
const missing = missingCredentials(process.env);
|
|
42
|
+
if (missing.length > 0 && command !== "export") {
|
|
43
|
+
await startUnconfiguredServer({ mode: command === "admin" ? "admin" : "chat", missing });
|
|
44
|
+
}
|
|
63
45
|
else {
|
|
64
|
-
|
|
46
|
+
const adminUrl = process.env.CAELO_ADMIN_URL;
|
|
47
|
+
const token = process.env.CAELO_MCP_TOKEN;
|
|
48
|
+
if (!adminUrl || !token) {
|
|
49
|
+
console.error(`${missing.join(" and ")} not set — export needs CAELO_ADMIN_URL and an ADMIN-scoped ` +
|
|
50
|
+
"CAELO_MCP_TOKEN minted at /settings/mcp on your admin install");
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
|
53
|
+
// #764 — run as the admin's own release (re-execs on a mismatch).
|
|
54
|
+
await ensureAdminRelease({ adminUrl, token, bin: "caelo-mcp-server" });
|
|
55
|
+
if (command === "export") {
|
|
56
|
+
const outFlag = process.argv.indexOf("--out");
|
|
57
|
+
const outDir = outFlag !== -1 ? process.argv[outFlag + 1] : undefined;
|
|
58
|
+
if (outFlag !== -1 && !outDir) {
|
|
59
|
+
console.error("--out needs a directory argument");
|
|
60
|
+
process.exit(2);
|
|
61
|
+
}
|
|
62
|
+
const written = await runExport({ adminUrl, token, outDir: outDir ?? process.cwd() });
|
|
63
|
+
for (const path of written)
|
|
64
|
+
console.log(path);
|
|
65
|
+
console.log(`exported ${written.length} file(s) — re-run after skills or site memory change`);
|
|
66
|
+
}
|
|
67
|
+
else if (command === "admin") {
|
|
68
|
+
await startAdminMcpServer({ adminUrl, token });
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
await startMcpServer({ adminUrl, token });
|
|
72
|
+
}
|
|
65
73
|
}
|
|
66
74
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,mCAAmC;AAEnC
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -7,11 +7,5 @@ export interface StartOpts {
|
|
|
7
7
|
/** Poll interval while waiting on an approval; tests shorten it. */
|
|
8
8
|
readonly approvalPollMs?: number;
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
11
|
-
* MCP `instructions` for the chat-scope server (#552), injected into the
|
|
12
|
-
* connecting agent's context so it uses caelo_chat as a conversation with
|
|
13
|
-
* the site's own agent rather than a one-shot command.
|
|
14
|
-
*/
|
|
15
|
-
export declare const CHAT_MCP_INSTRUCTIONS: string;
|
|
16
10
|
export declare function startMcpServer(opts: StartOpts): Promise<void>;
|
|
17
11
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAQ/E,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAKD,wBAAsB,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBnE"}
|
package/dist/server.js
CHANGED
|
@@ -1,273 +1,38 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MPL-2.0
|
|
2
2
|
/**
|
|
3
|
-
* MCP server
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* pages.list internally → text response). Same auth surface, same
|
|
9
|
-
* actor scope, same audit trail.
|
|
3
|
+
* The chat-scope stdio MCP server: `caelo_chat` plus its approval wait and
|
|
4
|
+
* the image upload companion, bound to a stdio transport. The server itself
|
|
5
|
+
* (tools, replies, approval waits) is ./surfaces/chat-surface.ts, shared
|
|
6
|
+
* with the admin's remote endpoint (issue #806); this module backs it with
|
|
7
|
+
* HTTP calls to the admin install's `/api/mcp/*` endpoints.
|
|
10
8
|
*/
|
|
11
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
12
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
|
-
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
14
|
-
import { z } from "zod";
|
|
15
10
|
import { resolveApprovalWaitMs } from "./approval-wait.js";
|
|
16
|
-
import { describeDecisions, describeReady, describeSuperseded, describeWaiting, settleChatApprovals, } from "./chat-approval.js";
|
|
17
11
|
import { fetchChatApprovalState, resumeChat, sendChat } from "./chat-bridge.js";
|
|
18
12
|
import { resolveTimeoutMs } from "./http.js";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* connecting agent's context so it uses caelo_chat as a conversation with
|
|
23
|
-
* the site's own agent rather than a one-shot command.
|
|
24
|
-
*/
|
|
25
|
-
export const CHAT_MCP_INSTRUCTIONS = [
|
|
26
|
-
"This server talks to a Caelo CMS site's own AI agent through caelo_chat; describe the outcome you want and the agent decides how to build it.",
|
|
27
|
-
"Pass the chatSessionId from a reply to continue the same conversation; omit it only to start a new one.",
|
|
28
|
-
"Changes land on that chat's preview branch; nothing reaches the live site until the operator publishes in the Caelo admin. Some actions wait for the operator's approval click there.",
|
|
29
|
-
"When a reply says the turn is waiting for the operator's approval, call caelo_await_approval with the chatSessionId: it waits for the decision and returns your turn's continuation. Never re-send the request; you cannot approve anything yourself.",
|
|
30
|
-
].join("\n");
|
|
31
|
-
const awaitApprovalInput = z.object({ chatSessionId: z.string().uuid() }).strict();
|
|
13
|
+
import { uploadImages } from "./image-upload.js";
|
|
14
|
+
import { createChatMcpServer } from "./surfaces/chat-surface.js";
|
|
15
|
+
import { ownVersion } from "./version-handshake.js";
|
|
32
16
|
/** The resume request's timeout (`resumeChat`), reserved out of a call's budget. */
|
|
33
17
|
const resumeTimeoutMs = () => resolveTimeoutMs(30_000);
|
|
34
|
-
/** Headroom for the state reads around a resume in caelo_await_approval. */
|
|
35
|
-
const RESUME_SLACK_MS = 5_000;
|
|
36
|
-
/** Issue #658 — keep waiting on the approval cards a caelo_chat turn paused on. */
|
|
37
|
-
const AWAIT_APPROVAL_TOOL = {
|
|
38
|
-
name: "caelo_await_approval",
|
|
39
|
-
description: "Wait for the operator's Approve/Reject on the approval cards your caelo_chat turn paused on, then " +
|
|
40
|
-
"continue that turn and return its reply (the change applied, or the agent's reaction to a rejection). " +
|
|
41
|
-
"Use it when a caelo_chat reply says it is still waiting for approval. You cannot approve anything " +
|
|
42
|
-
"yourself — only the operator can, in the Caelo admin.",
|
|
43
|
-
inputSchema: {
|
|
44
|
-
type: "object",
|
|
45
|
-
properties: {
|
|
46
|
-
chatSessionId: {
|
|
47
|
-
type: "string",
|
|
48
|
-
description: "The chatSessionId from the caelo_chat reply that is waiting.",
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
required: ["chatSessionId"],
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
/**
|
|
55
|
-
* The tool result for a caelo_chat exchange: every reply in order (the first
|
|
56
|
-
* turn, then each resumed one), the decisions in between, and — while cards
|
|
57
|
-
* still wait — the next step.
|
|
58
|
-
*/
|
|
59
|
-
function chatResult(first, settled, chatSessionId) {
|
|
60
|
-
const turns = first ? [first, ...settled.turns] : [...settled.turns];
|
|
61
|
-
const last = turns.at(-1);
|
|
62
|
-
const text = [
|
|
63
|
-
...(first ? [first.assistant] : []),
|
|
64
|
-
...(settled.decided.length > 0 ? [describeDecisions(settled.decided)] : []),
|
|
65
|
-
...settled.turns.map((t) => t.assistant),
|
|
66
|
-
...(settled.kind === "pending" ? [describeWaiting(chatSessionId, settled.waiting)] : []),
|
|
67
|
-
...(settled.kind === "superseded" ? [describeSuperseded(settled.dropped)] : []),
|
|
68
|
-
...(settled.kind === "ready" ? [describeReady(chatSessionId)] : []),
|
|
69
|
-
]
|
|
70
|
-
.filter((t) => t.trim().length > 0)
|
|
71
|
-
.join("\n\n");
|
|
72
|
-
const reply = text.length > 0
|
|
73
|
-
? text
|
|
74
|
-
: "Nothing of yours waits for the operator's approval in this chat; continue with caelo_chat.";
|
|
75
|
-
return {
|
|
76
|
-
content: [
|
|
77
|
-
{ type: "text", text: reply },
|
|
78
|
-
{
|
|
79
|
-
type: "text",
|
|
80
|
-
text: JSON.stringify({
|
|
81
|
-
chatSessionId,
|
|
82
|
-
requestId: last?.requestId ?? null,
|
|
83
|
-
toolCalls: turns.flatMap((t) => t.toolCalls),
|
|
84
|
-
pendingProposals: last?.pendingProposals ?? 0,
|
|
85
|
-
costMicrocents: turns.reduce((sum, t) => sum + t.costMicrocents, 0),
|
|
86
|
-
// Non-empty while the turn waits on the operator.
|
|
87
|
-
pendingApprovals: settled.kind === "pending"
|
|
88
|
-
? settled.waiting.map((a) => ({ approvalId: a.approvalId, toolName: a.toolName }))
|
|
89
|
-
: [],
|
|
90
|
-
}, null, 2),
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
const caeloChatInputSchema = z
|
|
96
|
-
.object({
|
|
97
|
-
attachments: z.array(uploadedImageSchema).max(4).optional(),
|
|
98
|
-
message: z.string().min(1).max(50_000),
|
|
99
|
-
chatSessionId: z.string().uuid().optional(),
|
|
100
|
-
pageId: z.string().uuid().optional(),
|
|
101
|
-
})
|
|
102
|
-
.strict();
|
|
103
18
|
export async function startMcpServer(opts) {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
19
|
+
const target = { adminUrl: opts.adminUrl, token: opts.token };
|
|
20
|
+
const server = createChatMcpServer({
|
|
21
|
+
sendChat: (input) => sendChat({
|
|
22
|
+
...target,
|
|
23
|
+
message: input.message,
|
|
24
|
+
...(input.attachments ? { attachments: input.attachments } : {}),
|
|
25
|
+
...(input.chatSessionId ? { chatSessionId: input.chatSessionId } : {}),
|
|
26
|
+
...(input.pageId ? { pageId: input.pageId } : {}),
|
|
27
|
+
}),
|
|
28
|
+
resumeChat: (chatSessionId) => resumeChat({ ...target, chatSessionId }),
|
|
29
|
+
fetchChatApprovalState: (chatSessionId) => fetchChatApprovalState({ ...target, chatSessionId }),
|
|
30
|
+
uploadImages: (args) => uploadImages(target, args),
|
|
107
31
|
}, {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
tools: [
|
|
113
|
-
UPLOAD_IMAGES_TOOL,
|
|
114
|
-
AWAIT_APPROVAL_TOOL,
|
|
115
|
-
{
|
|
116
|
-
name: "caelo_chat",
|
|
117
|
-
description: "Talk to your Caelo CMS install's AI agent. Same agent that powers the live-edit chat overlay — it can read pages, propose edits, queue Owner-approval proposals, summarise plugin data. Continues an existing chat session (when chatSessionId is supplied) or starts a fresh one. Returns the agent's reply text plus structured tool-call summaries.",
|
|
118
|
-
inputSchema: {
|
|
119
|
-
type: "object",
|
|
120
|
-
required: ["message"],
|
|
121
|
-
properties: {
|
|
122
|
-
attachments: {
|
|
123
|
-
type: "array",
|
|
124
|
-
maxItems: 4,
|
|
125
|
-
description: "Attachments returned by caelo_upload_images.",
|
|
126
|
-
items: z.toJSONSchema(uploadedImageSchema),
|
|
127
|
-
},
|
|
128
|
-
message: {
|
|
129
|
-
type: "string",
|
|
130
|
-
description: "What you want to say to the Caelo agent.",
|
|
131
|
-
},
|
|
132
|
-
chatSessionId: {
|
|
133
|
-
type: "string",
|
|
134
|
-
description: "Optional. Chat session UUID to continue. Omit to start a fresh page-unbound chat.",
|
|
135
|
-
},
|
|
136
|
-
pageId: {
|
|
137
|
-
type: "string",
|
|
138
|
-
description: "Optional. Bind a NEW chat to one page so the agent's page-context block is populated. Same shape as /edit?page=<id>.",
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
],
|
|
144
|
-
}));
|
|
145
|
-
server.setRequestHandler(CallToolRequestSchema, async (req, extra) => {
|
|
146
|
-
// Progress notifications keep progress-aware clients from timing the
|
|
147
|
-
// call out while it waits on the operator (only when they asked).
|
|
148
|
-
const progressToken = req.params._meta?.progressToken;
|
|
149
|
-
const onPending = progressToken === undefined
|
|
150
|
-
? undefined
|
|
151
|
-
: async (elapsedMs) => {
|
|
152
|
-
await extra.sendNotification({
|
|
153
|
-
method: "notifications/progress",
|
|
154
|
-
params: {
|
|
155
|
-
progressToken,
|
|
156
|
-
progress: Math.round(elapsedMs / 1000),
|
|
157
|
-
message: "Waiting for the operator to approve or reject in the Caelo admin…",
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
};
|
|
161
|
-
/** Wait for the operator, then continue the paused turn (issue #658). */
|
|
162
|
-
const settle = (chatSessionId, waitMs) => settleChatApprovals({
|
|
163
|
-
chatSessionId,
|
|
164
|
-
fetchState: () => fetchChatApprovalState({ ...opts, chatSessionId }),
|
|
165
|
-
resume: () => resumeChat({ ...opts, chatSessionId }),
|
|
166
|
-
waitMs,
|
|
167
|
-
// The resume POST's whole timeout stays inside the call's budget.
|
|
168
|
-
resumeReserveMs: resumeTimeoutMs(),
|
|
169
|
-
...(opts.approvalPollMs !== undefined ? { intervalMs: opts.approvalPollMs } : {}),
|
|
170
|
-
...(onPending ? { onPending } : {}),
|
|
171
|
-
});
|
|
172
|
-
if (req.params.name === UPLOAD_IMAGES_TOOL.name) {
|
|
173
|
-
try {
|
|
174
|
-
return await uploadImages(opts, req.params.arguments);
|
|
175
|
-
}
|
|
176
|
-
catch (e) {
|
|
177
|
-
return { isError: true, content: [{ type: "text", text: String(e) }] };
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
if (req.params.name === AWAIT_APPROVAL_TOOL.name) {
|
|
181
|
-
const parsed = awaitApprovalInput.safeParse(req.params.arguments ?? {});
|
|
182
|
-
if (!parsed.success) {
|
|
183
|
-
return {
|
|
184
|
-
isError: true,
|
|
185
|
-
content: [{ type: "text", text: `invalid arguments: ${parsed.error.message}` }],
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
try {
|
|
189
|
-
// Room for at least one resume, even with a short configured wait —
|
|
190
|
-
// otherwise a decided turn could never continue.
|
|
191
|
-
const settled = await settle(parsed.data.chatSessionId, Math.max(resolveApprovalWaitMs(), resumeTimeoutMs() + RESUME_SLACK_MS));
|
|
192
|
-
return chatResult(null, settled, parsed.data.chatSessionId);
|
|
193
|
-
}
|
|
194
|
-
catch (e) {
|
|
195
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
196
|
-
return {
|
|
197
|
-
isError: true,
|
|
198
|
-
content: [{ type: "text", text: `caelo_await_approval failed: ${msg}` }],
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
if (req.params.name !== "caelo_chat") {
|
|
203
|
-
return {
|
|
204
|
-
isError: true,
|
|
205
|
-
content: [{ type: "text", text: `unknown tool: ${req.params.name}` }],
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
const parsed = caeloChatInputSchema.safeParse(req.params.arguments ?? {});
|
|
209
|
-
if (!parsed.success) {
|
|
210
|
-
return {
|
|
211
|
-
isError: true,
|
|
212
|
-
content: [{ type: "text", text: `invalid arguments: ${parsed.error.message}` }],
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
try {
|
|
216
|
-
const startedAt = Date.now();
|
|
217
|
-
const result = await sendChat({
|
|
218
|
-
adminUrl: opts.adminUrl,
|
|
219
|
-
token: opts.token,
|
|
220
|
-
message: parsed.data.message,
|
|
221
|
-
...(parsed.data.attachments ? { attachments: parsed.data.attachments } : {}),
|
|
222
|
-
...(parsed.data.chatSessionId ? { chatSessionId: parsed.data.chatSessionId } : {}),
|
|
223
|
-
...(parsed.data.pageId ? { pageId: parsed.data.pageId } : {}),
|
|
224
|
-
});
|
|
225
|
-
// Issue #658 — the turn paused on approval cards: wait for the
|
|
226
|
-
// operator's decision within what is left of the wait bound, then
|
|
227
|
-
// continue this same turn and return its reply too.
|
|
228
|
-
if (result.pendingApprovals.length === 0) {
|
|
229
|
-
return chatResult(result, { kind: "settled", decided: [], turns: [] }, result.chatSessionId);
|
|
230
|
-
}
|
|
231
|
-
let settled;
|
|
232
|
-
try {
|
|
233
|
-
settled = await settle(result.chatSessionId, Math.max(0, resolveApprovalWaitMs() - (Date.now() - startedAt)));
|
|
234
|
-
}
|
|
235
|
-
catch (e) {
|
|
236
|
-
// The turn ran and its session exists: keep both in the result so
|
|
237
|
-
// the agent can continue with caelo_await_approval instead of
|
|
238
|
-
// retrying into duplicate work (codex review).
|
|
239
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
240
|
-
const waiting = chatResult(result, {
|
|
241
|
-
kind: "pending",
|
|
242
|
-
decided: [],
|
|
243
|
-
turns: [],
|
|
244
|
-
waiting: result.pendingApprovals.map((a) => ({
|
|
245
|
-
...a,
|
|
246
|
-
status: "pending",
|
|
247
|
-
approved: null,
|
|
248
|
-
reason: null,
|
|
249
|
-
})),
|
|
250
|
-
}, result.chatSessionId);
|
|
251
|
-
return {
|
|
252
|
-
...waiting,
|
|
253
|
-
content: [
|
|
254
|
-
...waiting.content,
|
|
255
|
-
{
|
|
256
|
-
type: "text",
|
|
257
|
-
text: `Checking the operator's decision failed (${msg}); the request still exists. Call caelo_await_approval with chatSessionId "${result.chatSessionId}".`,
|
|
258
|
-
},
|
|
259
|
-
],
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
return chatResult(result, settled, result.chatSessionId);
|
|
263
|
-
}
|
|
264
|
-
catch (e) {
|
|
265
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
266
|
-
return {
|
|
267
|
-
isError: true,
|
|
268
|
-
content: [{ type: "text", text: `caelo_chat failed: ${msg}` }],
|
|
269
|
-
};
|
|
270
|
-
}
|
|
32
|
+
serverVersion: ownVersion(),
|
|
33
|
+
approvalWaitMs: resolveApprovalWaitMs,
|
|
34
|
+
resumeTimeoutMs,
|
|
35
|
+
...(opts.approvalPollMs !== undefined ? { approvalPollMs: opts.approvalPollMs } : {}),
|
|
271
36
|
});
|
|
272
37
|
await server.connect(opts.transport ?? new StdioServerTransport());
|
|
273
38
|
}
|