import { z } from "zod";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { ElmapiClient } from "../client.js";
export function registerContentTools(
server: McpServer,
client: ElmapiClient
): void {
// ── list_entries ──────────────────────────────────────────────────
server.registerTool("list_entries", {
title: "List Entries",
description:
"List content entries for a collection with advanced filtering, sorting, and pagination. " +
"Use the 'where' parameter for powerful queries. Read the 'query-reference' resource for full documentation on operators and examples.",
inputSchema: {
collection_slug: z.string().describe("The collection slug"),
where: z
.record(z.string(), z.unknown())
.optional()
.describe(
"Filter conditions as a nested object. Supports operators: eq, lt, lte, gt, gte, not, like, in, not_in, null, not_null, between, not_between. " +
"Simple: { \"status\": \"published\" }. " +
"With operators: { \"price\": { \"lt\": 50 }, \"title\": { \"like\": \"news\" } }. " +
"OR group: { \"or\": [{ \"tags\": \"clearance\" }, { \"campaign\": { \"name\": \"Summer\" } }] }. " +
"Relation filter: { \"author\": { \"name\": { \"eq\": \"John\" } } }. " +
"Core columns (id, uuid, locale, status, created_at, updated_at, published_at) can be filtered directly."
),
locale: z
.string()
.optional()
.describe("Filter by locale (e.g. 'en')"),
state: z
.string()
.optional()
.describe("Filter by state: 'only_draft' or 'with_draft'. Defaults to published entries only."),
sort: z
.string()
.optional()
.describe(
"Sort by field:direction, comma-separated for multiple. " +
"Examples: 'created_at:desc', 'title:asc,created_at:desc'. " +
"Supports core columns (id, created_at, updated_at, published_at) and custom field names."
),
paginate: z
.number()
.optional()
.describe("Enable pagination with N items per page. Returns paginated response with meta data. Overrides limit/offset."),
limit: z
.number()
.optional()
.describe("Limit the number of results (ignored if paginate is set)"),
offset: z
.number()
.optional()
.describe("Skip N results (requires limit to be set)"),
first: z
.boolean()
.optional()
.describe("If true, return only the first matching entry as a single object instead of an array"),
count: z
.boolean()
.optional()
.describe("If true, return only the count of matching entries: { count: N }"),
exclude: z
.string()
.optional()
.describe("Comma-separated field names to exclude from response (e.g. 'content,excerpt')"),
},
}, async ({ collection_slug, where, locale, state, sort, paginate, limit, offset, first, count, exclude }) => {
const params: Record<string, unknown> = {};
if (locale) params.locale = locale;
if (state) params.state = state;
if (sort) params.sort = sort;
if (paginate) params.paginate = paginate;
if (limit) params.limit = limit;
if (offset) params.offset = offset;
if (first) params.first = 1;
if (count) params.count = 1;
if (exclude) params.exclude = exclude;
// Pass where as a nested object — the client will flatten to bracket notation
if (where) {
params.where = where;
}
const result = await client.get(`/${collection_slug}`, params);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
});
// ── get_entry ─────────────────────────────────────────────────────
server.registerTool("get_entry", {
title: "Get Entry",
description: "Get a single content entry by UUID",
inputSchema: {
collection_slug: z.string().describe("The collection slug"),
uuid: z.string().describe("The entry UUID"),
locale: z.string().optional().describe("Locale code"),
},
}, async ({ collection_slug, uuid, locale }) => {
const params: Record<string, string> = {};
if (locale) params.locale = locale;
const result = await client.get(`/${collection_slug}/${uuid}`, params);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
});
// ── create_entry ──────────────────────────────────────────────────
server.registerTool("create_entry", {
title: "Create Entry",
description: "Create a new content entry in a collection",
inputSchema: {
collection_slug: z.string().describe("The collection slug"),
data: z
.record(z.string(), z.unknown())
.describe(
"Object where keys are field names and values are the content (e.g. { title: 'My Post', slug: 'my-post' })"
),
status: z
.string()
.optional()
.describe("Publication status: 'published' or 'draft' (default)"),
locale: z.string().optional().describe("Locale code (e.g. 'en')"),
},
}, async ({ collection_slug, data, status, locale }) => {
const body: Record<string, unknown> = { data };
if (status) body.status = status;
if (locale) body.locale = locale;
const result = await client.post(`/${collection_slug}`, body);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
});
// ── update_entry ──────────────────────────────────────────────────
server.registerTool("update_entry", {
title: "Update Entry",
description: "Update an existing content entry",
inputSchema: {
collection_slug: z.string().describe("The collection slug"),
uuid: z.string().describe("The entry UUID"),
data: z
.record(z.string(), z.unknown())
.describe("Object with field names and their new values"),
status: z
.string()
.optional()
.describe("Publication status: 'published' or 'draft'"),
locale: z.string().optional().describe("Locale code"),
},
}, async ({ collection_slug, uuid, data, status, locale }) => {
const body: Record<string, unknown> = { data };
if (status) body.status = status;
if (locale) body.locale = locale;
const result = await client.put(`/${collection_slug}/${uuid}`, body);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
});
// ── delete_entry ──────────────────────────────────────────────────
server.registerTool("delete_entry", {
title: "Delete Entry",
description:
"Soft-delete a content entry (moves to trash). Can be restored from the admin panel.",
inputSchema: {
collection_slug: z.string().describe("The collection slug"),
uuid: z.string().describe("The entry UUID"),
},
}, async ({ collection_slug, uuid }) => {
const result = await client.delete(`/${collection_slug}/${uuid}`);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
});
}