@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.
Files changed (46) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +7 -0
  3. package/dist/admin-server.d.ts +1 -15
  4. package/dist/admin-server.d.ts.map +1 -1
  5. package/dist/admin-server.js +72 -302
  6. package/dist/admin-server.js.map +1 -1
  7. package/dist/admin.js +7 -8
  8. package/dist/admin.js.map +1 -1
  9. package/dist/chat-bridge.d.ts +1 -1
  10. package/dist/chat-bridge.d.ts.map +1 -1
  11. package/dist/image-upload.d.ts +2 -75
  12. package/dist/image-upload.d.ts.map +1 -1
  13. package/dist/image-upload.js +40 -134
  14. package/dist/image-upload.js.map +1 -1
  15. package/dist/index.js +38 -30
  16. package/dist/index.js.map +1 -1
  17. package/dist/server.d.ts +0 -6
  18. package/dist/server.d.ts.map +1 -1
  19. package/dist/server.js +24 -259
  20. package/dist/server.js.map +1 -1
  21. package/dist/surfaces/admin-surface.d.ts +106 -0
  22. package/dist/surfaces/admin-surface.d.ts.map +1 -0
  23. package/dist/surfaces/admin-surface.js +183 -0
  24. package/dist/surfaces/admin-surface.js.map +1 -0
  25. package/dist/surfaces/chat-surface.d.ts +48 -0
  26. package/dist/surfaces/chat-surface.d.ts.map +1 -0
  27. package/dist/surfaces/chat-surface.js +190 -0
  28. package/dist/surfaces/chat-surface.js.map +1 -0
  29. package/dist/surfaces/index.d.ts +15 -0
  30. package/dist/surfaces/index.d.ts.map +1 -0
  31. package/dist/surfaces/index.js +15 -0
  32. package/dist/surfaces/index.js.map +1 -0
  33. package/dist/surfaces/upload.d.ts +32 -0
  34. package/dist/surfaces/upload.d.ts.map +1 -0
  35. package/dist/surfaces/upload.js +58 -0
  36. package/dist/surfaces/upload.js.map +1 -0
  37. package/dist/tool-definitions.d.ts +465 -0
  38. package/dist/tool-definitions.d.ts.map +1 -0
  39. package/dist/tool-definitions.js +256 -0
  40. package/dist/tool-definitions.js.map +1 -0
  41. package/dist/unconfigured.d.ts +26 -0
  42. package/dist/unconfigured.d.ts.map +1 -0
  43. package/dist/unconfigured.js +82 -0
  44. package/dist/unconfigured.js.map +1 -0
  45. package/package.json +10 -1
  46. package/power-catalogue.json +8116 -0
@@ -1,121 +1,47 @@
1
1
  // SPDX-License-Identifier: MPL-2.0
2
- /** Image upload transport shared by chat and admin MCP servers. */
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
- const MAX_BYTES = 5 * 1024 * 1024;
10
- /** Uploaded media references accepted by caelo_chat; never accept object-store keys. */
11
- export const uploadedImageSchema = z
12
- .object({
13
- assetId: z.string().uuid(),
14
- mime: z.enum(["image/png", "image/jpeg", "image/webp", "image/gif"]),
15
- alt: z.string().max(2048).optional(),
16
- })
17
- .strict();
18
- const imageInput = z
19
- .object({
20
- filePath: z.string().min(1).max(4096).optional(),
21
- base64: z
22
- .string()
23
- .min(4)
24
- .max(Math.ceil(MAX_BYTES / 3) * 4)
25
- .regex(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/)
26
- .optional(),
27
- filename: z.string().min(1).max(512).optional(),
28
- alt: z.string().max(2048).optional(),
29
- reference: z.boolean().optional(),
30
- })
31
- .strict()
32
- .refine((i) => (i.filePath === undefined) !== (i.base64 === undefined), "Supply exactly one of filePath or base64");
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
- const parsed = uploadImagesInput.parse(input);
82
- const results = [];
83
- for (const [index, image] of parsed.images.entries()) {
84
- try {
85
- let bytes;
86
- if (image.filePath) {
87
- const file = await open(image.filePath, constants.O_RDONLY | constants.O_NONBLOCK);
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
- const attachment = uploadedImageSchema.parse({
136
- assetId: value.assetId,
137
- mime: value.mime,
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
@@ -1 +1 @@
1
- {"version":3,"file":"image-upload.js","sourceRoot":"","sources":["../src/image-upload.ts"],"names":[],"mappings":"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"}
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 === "export") {
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
- await startMcpServer({ adminUrl, token });
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;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;AAC7C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;AAE1C,IAAI,CAAC,QAAQ,EAAE,CAAC;IACd,OAAO,CAAC,KAAK,CACX,2FAA2F,CAC5F,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AACD,IAAI,CAAC,KAAK,EAAE,CAAC;IACX,OAAO,CAAC,KAAK,CAAC,2EAA2E,CAAC,CAAC;IAC3F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,kEAAkE;AAClE,MAAM,kBAAkB,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,kBAAkB,EAAE,CAAC,CAAC;AAEvE,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAEhC,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;IACzB,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACtE,IAAI,OAAO,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QAC9B,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACtF,KAAK,MAAM,IAAI,IAAI,OAAO;QAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,YAAY,OAAO,CAAC,MAAM,sDAAsD,CAAC,CAAC;AAChG,CAAC;KAAM,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;IAC/B,MAAM,mBAAmB,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;AACjD,CAAC;KAAM,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;IACjC,OAAO,CAAC,KAAK,CAAC,oBAAoB,OAAO,8CAA8C,CAAC,CAAC;IACzF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;KAAM,CAAC;IACN,MAAM,cAAc,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5C,CAAC"}
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
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAiB/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;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,QAKtB,CAAC;AAsFb,wBAAsB,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAgMnE"}
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 construction. Registers `caelo_chat` and the image upload companion
4
- * and binds it to a stdio transport. Routing edits through one conversational tool is
5
- * deliberate: the remote agent talks to Caelo's chat-runner the same
6
- * way a human in the browser does. Browse / publish / propose actions
7
- * happen through the chat ("which pages exist?" → agent calls
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 { UPLOAD_IMAGES_TOOL, uploadedImageSchema, uploadImages } from "./image-upload.js";
20
- /**
21
- * MCP `instructions` for the chat-scope server (#552), injected into the
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 server = new Server({
105
- name: "caelo-mcp-server",
106
- version: "0.1.0",
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
- capabilities: { tools: {} },
109
- instructions: CHAT_MCP_INSTRUCTIONS,
110
- });
111
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
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
  }