@cloudhal/mcp-server 0.0.0-stage → 0.1.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.
Files changed (105) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +18 -2
  3. package/dist/src/analytics-reports.d.ts +164 -0
  4. package/dist/src/analytics-reports.js +632 -0
  5. package/dist/src/analytics-tools.d.ts +4 -0
  6. package/dist/src/analytics-tools.js +597 -0
  7. package/dist/src/asset-tools.d.ts +5 -0
  8. package/dist/src/asset-tools.js +685 -0
  9. package/dist/src/body-tools.d.ts +46 -0
  10. package/dist/src/body-tools.js +708 -0
  11. package/dist/src/changes.d.ts +10 -0
  12. package/dist/src/changes.js +112 -0
  13. package/dist/src/chrome-tools.d.ts +8 -0
  14. package/dist/src/chrome-tools.js +207 -0
  15. package/dist/src/comment-tools.d.ts +9 -0
  16. package/dist/src/comment-tools.js +743 -0
  17. package/dist/src/comments.d.ts +184 -0
  18. package/dist/src/comments.js +408 -0
  19. package/dist/src/common.d.ts +258 -0
  20. package/dist/src/common.js +488 -0
  21. package/dist/src/content-shared.d.ts +50 -0
  22. package/dist/src/content-shared.js +102 -0
  23. package/dist/src/design-tools.d.ts +9 -0
  24. package/dist/src/design-tools.js +349 -0
  25. package/dist/src/elevation-tools.d.ts +3 -0
  26. package/dist/src/elevation-tools.js +331 -0
  27. package/dist/src/entry-terms.d.ts +14 -0
  28. package/dist/src/entry-terms.js +51 -0
  29. package/dist/src/entry-tools.d.ts +4 -0
  30. package/dist/src/entry-tools.js +442 -0
  31. package/dist/src/http.d.ts +48 -0
  32. package/dist/src/http.js +101 -0
  33. package/dist/src/index.d.ts +10 -0
  34. package/dist/src/index.js +10 -0
  35. package/dist/src/menu-tools.d.ts +41 -0
  36. package/dist/src/menu-tools.js +1058 -0
  37. package/dist/src/page-tools.d.ts +5 -0
  38. package/dist/src/page-tools.js +311 -0
  39. package/dist/src/preview-doc.d.ts +9 -0
  40. package/dist/src/preview-doc.js +56 -0
  41. package/dist/src/preview-view.d.ts +19 -0
  42. package/dist/src/preview-view.js +71 -0
  43. package/dist/src/publish-tools.d.ts +10 -0
  44. package/dist/src/publish-tools.js +248 -0
  45. package/dist/src/render/cdp.d.ts +29 -0
  46. package/dist/src/render/cdp.js +190 -0
  47. package/dist/src/render/checks/components.d.ts +5 -0
  48. package/dist/src/render/checks/components.js +95 -0
  49. package/dist/src/render/checks/content.d.ts +5 -0
  50. package/dist/src/render/checks/content.js +63 -0
  51. package/dist/src/render/checks/contrast.d.ts +5 -0
  52. package/dist/src/render/checks/contrast.js +72 -0
  53. package/dist/src/render/checks/index.d.ts +1 -0
  54. package/dist/src/render/checks/index.js +29 -0
  55. package/dist/src/render/checks/layout.d.ts +5 -0
  56. package/dist/src/render/checks/layout.js +44 -0
  57. package/dist/src/render/checks/motion.d.ts +5 -0
  58. package/dist/src/render/checks/motion.js +66 -0
  59. package/dist/src/render/checks/navigation.d.ts +5 -0
  60. package/dist/src/render/checks/navigation.js +78 -0
  61. package/dist/src/render/checks/prelude.d.ts +1 -0
  62. package/dist/src/render/checks/prelude.js +64 -0
  63. package/dist/src/render/limiter.d.ts +11 -0
  64. package/dist/src/render/limiter.js +29 -0
  65. package/dist/src/render/page.d.ts +19 -0
  66. package/dist/src/render/page.js +135 -0
  67. package/dist/src/render/types.d.ts +89 -0
  68. package/dist/src/render/types.js +140 -0
  69. package/dist/src/render-tools.d.ts +14 -0
  70. package/dist/src/render-tools.js +314 -0
  71. package/dist/src/render.d.ts +14 -0
  72. package/dist/src/render.js +33 -0
  73. package/dist/src/schedule-tools.d.ts +2 -0
  74. package/dist/src/schedule-tools.js +1596 -0
  75. package/dist/src/schedules.d.ts +292 -0
  76. package/dist/src/schedules.js +890 -0
  77. package/dist/src/schema-transforms.d.ts +14 -0
  78. package/dist/src/schema-transforms.js +50 -0
  79. package/dist/src/search-tools.d.ts +2 -0
  80. package/dist/src/search-tools.js +112 -0
  81. package/dist/src/server-approvals.d.ts +51 -0
  82. package/dist/src/server-approvals.js +273 -0
  83. package/dist/src/server-call.d.ts +28 -0
  84. package/dist/src/server-call.js +259 -0
  85. package/dist/src/server-instructions.d.ts +4 -0
  86. package/dist/src/server-instructions.js +74 -0
  87. package/dist/src/server-resources.d.ts +4 -0
  88. package/dist/src/server-resources.js +83 -0
  89. package/dist/src/server-schema.d.ts +20 -0
  90. package/dist/src/server-schema.js +82 -0
  91. package/dist/src/server.d.ts +80 -0
  92. package/dist/src/server.js +135 -0
  93. package/dist/src/stdio.d.ts +9 -0
  94. package/dist/src/stdio.js +18 -0
  95. package/dist/src/surface-tools.d.ts +4 -0
  96. package/dist/src/surface-tools.js +357 -0
  97. package/dist/src/taxonomy-tools.d.ts +16 -0
  98. package/dist/src/taxonomy-tools.js +1433 -0
  99. package/dist/src/terms.d.ts +47 -0
  100. package/dist/src/terms.js +271 -0
  101. package/dist/src/tools.d.ts +39 -0
  102. package/dist/src/tools.js +511 -0
  103. package/dist/src/translation-tools.d.ts +3 -0
  104. package/dist/src/translation-tools.js +422 -0
  105. package/package.json +86 -3
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Term assignment over MCP (RFC-0002 §6.2–§6.3): `terms: { [taxonomy]: { set?, add?, remove?,
3
+ * primary? } }` with values by id, slug or name, resolved by the store's `resolveTerms`. Nothing is
4
+ * ever created implicitly: an unknown or ambiguous value is an error with suggestions. Also the
5
+ * assignment deltas shown in approval prompts and written to the audit.
6
+ */
7
+ import type { ContentEntry, Site, Term } from "@cloudhal/core-contracts";
8
+ import { type JsonSchema, type ToolContext } from "./common.js";
9
+ export type Assignments = NonNullable<ContentEntry["terms"]>;
10
+ export interface TermOps {
11
+ set?: string[];
12
+ add?: string[];
13
+ remove?: string[];
14
+ primary?: string;
15
+ }
16
+ export type TermsInput = Record<string, TermOps>;
17
+ /** Input schema of `terms` on create_entry / update_entry. */
18
+ export declare const TERMS_INPUT: JsonSchema;
19
+ /** The deprecated `tags` input: an alias for `terms.tag.add`. */
20
+ export declare const TAGS_INPUT: JsonSchema;
21
+ /**
22
+ * Applies `input` to an entry's current assignments. Resolves every value first (all taxonomies),
23
+ * so one error lists every unknown value. Returns the new assignments (canonical: no empty lists).
24
+ */
25
+ export declare function applyTermsInput(ctx: ToolContext, site: Site, current: ContentEntry["terms"], input: TermsInput): Promise<ContentEntry["terms"]>;
26
+ /** What changed in one taxonomy's assignment. */
27
+ export interface AssignmentDelta {
28
+ readonly taxonomy: string;
29
+ readonly added: readonly string[];
30
+ readonly removed: readonly string[];
31
+ /** Present when the primary term changed (`to` absent: no primary any more). */
32
+ readonly primary?: {
33
+ readonly from?: string;
34
+ readonly to?: string;
35
+ };
36
+ }
37
+ /** Assignment deltas between two versions of an entry's `terms`, by taxonomy key (sorted). */
38
+ export declare function assignmentDeltas(before: ContentEntry["terms"], after: ContentEntry["terms"]): AssignmentDelta[];
39
+ /** Looks terms up once per prompt or audit row. */
40
+ export declare function termLookup(ctx: ToolContext): (id: string) => Promise<Term | undefined>;
41
+ /**
42
+ * `+tag ai, −tag llms, primary category → News` (slugs for membership, the quoted name for the
43
+ * primary). At most `max` parts; the rest are counted.
44
+ */
45
+ export declare function describeDeltas(deltas: readonly AssignmentDelta[], lookup: (id: string) => Promise<Term | undefined>, locale: string | undefined, max?: number): Promise<string>;
46
+ /** Deltas by slug, for the audit row (`input.effect.terms`). */
47
+ export declare function auditDeltas(deltas: readonly AssignmentDelta[], lookup: (id: string) => Promise<Term | undefined>): Promise<Record<string, unknown>>;
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Term assignment over MCP (RFC-0002 §6.2–§6.3): `terms: { [taxonomy]: { set?, add?, remove?,
3
+ * primary? } }` with values by id, slug or name, resolved by the store's `resolveTerms`. Nothing is
4
+ * ever created implicitly: an unknown or ambiguous value is an error with suggestions. Also the
5
+ * assignment deltas shown in approval prompts and written to the audit.
6
+ */
7
+
8
+
9
+ import { termName } from "@cloudhal/core-ts";
10
+ import {
11
+
12
+ quote,
13
+ SLUG_PATTERN,
14
+
15
+ ToolError,
16
+ toToolError,
17
+ } from "./common.js";
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+ const termRef = {
31
+ type: "string",
32
+ minLength: 1,
33
+ maxLength: 200,
34
+ description: "A term id, current or old slug, name in any locale, or synonym",
35
+ };
36
+ const termList = (desc ) => ({
37
+ type: "array",
38
+ description: desc,
39
+ items: termRef,
40
+ maxItems: 50,
41
+ uniqueItems: true,
42
+ });
43
+
44
+ /** Input schema of `terms` on create_entry / update_entry. */
45
+ export const TERMS_INPUT = {
46
+ type: "object",
47
+ description:
48
+ 'Categories, tags and other terms by taxonomy key (see list_taxonomies), e.g. {"category": {"primary": "News"}, "tag": {"add": ["AI"], "remove": ["llms"]}}. Values are ids, slugs, names or synonyms of existing terms (list_terms, resolve_terms); unknown values are an error, never created. `set` replaces the list, then `add` and `remove` apply; `primary` (required for review/published articles in required taxonomies) is added if missing. At most maxPerEntry terms per taxonomy.',
49
+ propertyNames: { pattern: SLUG_PATTERN, maxLength: 128 },
50
+ maxProperties: 10,
51
+ additionalProperties: {
52
+ type: "object",
53
+ additionalProperties: false,
54
+ properties: {
55
+ set: termList("Replace this taxonomy's terms with these (in display order)"),
56
+ add: termList("Add these terms"),
57
+ remove: termList("Remove these terms"),
58
+ primary: { ...termRef, description: "The main term (breadcrumbs, articleSection)" },
59
+ },
60
+ },
61
+ };
62
+
63
+ /** The deprecated `tags` input: an alias for `terms.tag.add`. */
64
+ export const TAGS_INPUT = {
65
+ type: "array",
66
+ deprecated: true,
67
+ description:
68
+ 'Deprecated: use terms.tag.add. Existing tags by slug or name; same rules as "terms".',
69
+ items: termRef,
70
+ maxItems: 50,
71
+ uniqueItems: true,
72
+ };
73
+
74
+ function lines(resolution , key , tax ) {
75
+ const s = resolution.suggestions.map((x) => `${x.slug} ("${quote(x.name, 40)}", ${x.termId})`);
76
+ if (resolution.reason === "ambiguous")
77
+ return `${key} "${quote(resolution.input, 40)}" matches several terms: ${s.join(", ")}; pass the term id`;
78
+ const next =
79
+ tax.allowNewTerms === "none"
80
+ ? `only a person adds a ${key}: pick an existing one (list_terms) or ask`
81
+ : `if it is really new, call propose_term(taxonomy: "${key}", name: …)`;
82
+ return `${key} "${quote(resolution.input, 40)}" matches no term${s.length ? ` (did you mean ${s.join(", ")}?)` : ""}; ${next}`;
83
+ }
84
+
85
+ function dedupe(values ) {
86
+ return [...new Set(values)];
87
+ }
88
+
89
+ /**
90
+ * Applies `input` to an entry's current assignments. Resolves every value first (all taxonomies),
91
+ * so one error lists every unknown value. Returns the new assignments (canonical: no empty lists).
92
+ */
93
+ export async function applyTermsInput(
94
+ ctx ,
95
+ site ,
96
+ current ,
97
+ input ,
98
+ ) {
99
+ const next = JSON.parse(JSON.stringify(current ?? {}));
100
+ const problems = [];
101
+ const unknown = [];
102
+ let ambiguous = false;
103
+ const resolved = [];
104
+ for (const [key, ops] of Object.entries(input)) {
105
+ const tax = await ctx.store.getTaxonomyByKey(site.id, key);
106
+ if (!tax) {
107
+ const keys = (await ctx.store.listTaxonomies(site.id)).map((t) => t.key);
108
+ throw new ToolError(
109
+ "unknown_taxonomy",
110
+ `this site has no taxonomy "${key}" (it has: ${keys.sort().join(", ") || "none"}); call list_taxonomies`,
111
+ );
112
+ }
113
+ const values = dedupe([
114
+ ...(ops.set ?? []),
115
+ ...(ops.add ?? []),
116
+ ...(ops.remove ?? []),
117
+ ...(ops.primary !== undefined ? [ops.primary] : []),
118
+ ]);
119
+ const map = new Map ();
120
+ if (values.length > 0) {
121
+ let resolutions ;
122
+ try {
123
+ resolutions = await ctx.store.resolveTerms(site.id, key, values);
124
+ } catch (e) {
125
+ throw toToolError(e);
126
+ }
127
+ for (const r of resolutions) {
128
+ if (r.matched) map.set(r.input, r.termId);
129
+ else {
130
+ if (r.reason === "ambiguous") ambiguous = true;
131
+ problems.push(lines(r, key, tax));
132
+ unknown.push({ taxonomy: key, input: r.input, suggestions: r.suggestions });
133
+ }
134
+ }
135
+ }
136
+ resolved.push([key, ops, map]);
137
+ }
138
+ if (problems.length > 0)
139
+ throw new ToolError(ambiguous ? "ambiguous_term" : "unknown_term", problems.join("; "), {
140
+ unknown,
141
+ });
142
+
143
+ for (const [key, ops, map] of resolved) {
144
+ const ids = (values ) =>
145
+ dedupe((values ?? []).map((v) => map.get(v) ));
146
+ const before = next[key];
147
+ let list = ops.set !== undefined ? ids(ops.set) : [...(before?.ids ?? [])];
148
+ for (const add of ids(ops.add)) if (!list.includes(add)) list.push(add);
149
+ const removed = new Set(ids(ops.remove));
150
+ list = list.filter((x) => !removed.has(x));
151
+ let primary = before?.primary;
152
+ if (ops.primary !== undefined) {
153
+ primary = map.get(ops.primary) ;
154
+ if (!list.includes(primary)) list.push(primary);
155
+ }
156
+ if (primary !== undefined && !list.includes(primary)) primary = undefined;
157
+ if (list.length === 0) delete next[key];
158
+ else
159
+ next[key] = {
160
+ ids: list ,
161
+ ...(primary !== undefined ? { primary } : {}),
162
+ };
163
+ }
164
+ return Object.keys(next).length > 0 ? next : undefined;
165
+ }
166
+
167
+ /** What changed in one taxonomy's assignment. */
168
+
169
+
170
+
171
+
172
+
173
+
174
+
175
+
176
+ /** Assignment deltas between two versions of an entry's `terms`, by taxonomy key (sorted). */
177
+ export function assignmentDeltas(
178
+ before ,
179
+ after ,
180
+ ) {
181
+ const keys = [...new Set([...Object.keys(before ?? {}), ...Object.keys(after ?? {})])].sort();
182
+ const out = [];
183
+ for (const key of keys) {
184
+ const b = before?.[key];
185
+ const a = after?.[key];
186
+ const added = ((a?.ids ?? []) ).filter(
187
+ (x) => !((b?.ids ?? []) ).includes(x),
188
+ );
189
+ const removed = ((b?.ids ?? []) ).filter(
190
+ (x) => !((a?.ids ?? []) ).includes(x),
191
+ );
192
+ const primaryChanged = (b?.primary ?? null) !== (a?.primary ?? null);
193
+ if (!added.length && !removed.length && !primaryChanged) continue;
194
+ out.push({
195
+ taxonomy: key,
196
+ added,
197
+ removed,
198
+ ...(primaryChanged
199
+ ? {
200
+ primary: {
201
+ ...(b?.primary ? { from: b.primary } : {}),
202
+ ...(a?.primary ? { to: a.primary } : {}),
203
+ },
204
+ }
205
+ : {}),
206
+ });
207
+ }
208
+ return out;
209
+ }
210
+
211
+ /** Looks terms up once per prompt or audit row. */
212
+ export function termLookup(ctx ) {
213
+ const cache = new Map ();
214
+ return (id) => {
215
+ let hit = cache.get(id);
216
+ if (!hit) {
217
+ hit = ctx.store.getTerm(id);
218
+ cache.set(id, hit);
219
+ }
220
+ return hit;
221
+ };
222
+ }
223
+
224
+ /**
225
+ * `+tag ai, −tag llms, primary category → News` (slugs for membership, the quoted name for the
226
+ * primary). At most `max` parts; the rest are counted.
227
+ */
228
+ export async function describeDeltas(
229
+ deltas ,
230
+ lookup ,
231
+ locale ,
232
+ max = 6,
233
+ ) {
234
+ const parts = [];
235
+ for (const d of deltas) {
236
+ for (const x of d.added) parts.push(`+${d.taxonomy} ${(await lookup(x))?.slug ?? x}`);
237
+ for (const x of d.removed) parts.push(`−${d.taxonomy} ${(await lookup(x))?.slug ?? x}`);
238
+ if (d.primary) {
239
+ const to = d.primary.to ? await lookup(d.primary.to) : undefined;
240
+ parts.push(
241
+ `primary ${d.taxonomy} → ${d.primary.to ? (to ? quote(termName(to, locale), 40) : d.primary.to) : "none"}`,
242
+ );
243
+ }
244
+ }
245
+ return parts.length > max
246
+ ? `${parts.slice(0, max).join(", ")}, … ${parts.length - max} more`
247
+ : parts.join(", ");
248
+ }
249
+
250
+ /** Deltas by slug, for the audit row (`input.effect.terms`). */
251
+ export async function auditDeltas(
252
+ deltas ,
253
+ lookup ,
254
+ ) {
255
+ const slug = async (x ) => (await lookup(x))?.slug ?? x;
256
+ const out = {};
257
+ for (const d of deltas)
258
+ out[d.taxonomy] = {
259
+ ...(d.added.length ? { added: await Promise.all(d.added.map(slug)) } : {}),
260
+ ...(d.removed.length ? { removed: await Promise.all(d.removed.map(slug)) } : {}),
261
+ ...(d.primary
262
+ ? {
263
+ primary: {
264
+ from: d.primary.from ? await slug(d.primary.from) : null,
265
+ to: d.primary.to ? await slug(d.primary.to) : null,
266
+ },
267
+ }
268
+ : {}),
269
+ };
270
+ return out;
271
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Cloudhal's semantic tool surface, defined independently of the MCP SDK so the same definitions
3
+ * serve stdio, HTTP and tests. Each tool carries: JSON Schema input/output, honest annotations,
4
+ * the scopes the policy engine checks, and a handler over `SiteGraphStore`.
5
+ *
6
+ * Mutations snapshot automatically (reason `agentMutation`, label = tool name) so every agent
7
+ * change is diffable and reversible. `publish` and `rollback` are approval-gated.
8
+ */
9
+ import type { AuditLog, AutoCheck, Decision, PolicyEngine, ToolPolicy } from "@cloudhal/agent-runtime";
10
+ import { type ToolContext, type ToolDefinition } from "./common.js";
11
+ export { type ApprovalChanges, type BuildObserver, type JsonSchema, type MediaToolOptions, notifyBuild, type RenderDeps, type ToolContext, type ToolDefinition, ToolError, type ToolErrorCode, toToolError, } from "./common.js";
12
+ export { previewDoc } from "./preview-doc.js";
13
+ export { ownOnlyPublish } from "./publish-tools.js";
14
+ export { hoistDefs, relaxImageRefs } from "./schema-transforms.js";
15
+ /** All tools, in the deterministic order `tools/list` exposes them (agents see this order). */
16
+ export declare function defineTools(): ToolDefinition[];
17
+ export declare function toolPolicies(tools: ToolDefinition[]): ToolPolicy[];
18
+ export interface CallResult {
19
+ decision: Decision;
20
+ /** Set when `auto` mode let a low-risk publish through without a person (English, for the agent). */
21
+ autoApproved?: string;
22
+ /** The `auto`-mode check behind a publish, whichever way it went. */
23
+ autoCheck?: AutoCheck;
24
+ /** Present when the call needs a human decision before running. */
25
+ approvalPrompt?: string;
26
+ result?: unknown;
27
+ }
28
+ /**
29
+ * Policy → audit → handler. Transports call this; it is the only path a tool runs through.
30
+ * `confirmed` is set by the transport once the human said yes (MRTR response or confirm token);
31
+ * `approved` once an out-of-band approval was redeemed — someone who could make the full call
32
+ * decided it (RFC-0005 §6), so an own limit on a publish no longer applies.
33
+ */
34
+ export declare function runTool(def: ToolDefinition, args: Record<string, unknown>, ctx: ToolContext, deps: {
35
+ policy: PolicyEngine;
36
+ audit?: AuditLog | undefined;
37
+ confirmed?: boolean | undefined;
38
+ approved?: boolean | undefined;
39
+ }): Promise<CallResult>;