contentrain_content_list
Retrieve and filter content entries from Contentrain's structured content management system for AI-driven content governance and localization workflows.
Instructions
List content entries (read-only). Returns data from .contentrain/ — do NOT manually create or modify content files.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| model | Yes | Model ID | |
| locale | No | Locale code (defaults to config default) | |
| filter | No | Filter criteria (collection only) | |
| resolve | No | Resolve relation fields to actual data | |
| limit | No | Max entries to return | |
| offset | No | Skip N entries |
Implementation Reference
- The listContent function, which is the actual business logic for listing content entries.
export async function listContent( projectRoot: string, model: ModelDefinition, opts: ListOpts, config: ContentrainConfig, ): Promise<unknown> { const cDir = resolveContentDir(projectRoot, model) const locale = opts.locale ?? config.locales.default switch (model.kind) { case 'singleton': { const data = await readJson<Record<string, unknown>>(resolveJsonFilePath(cDir, model, locale)) return { kind: 'singleton', data: data ?? {}, locale } } case 'collection': { const data = await readJson<Record<string, Record<string, unknown>>>(resolveJsonFilePath(cDir, model, locale)) ?? {} let entries: Array<Record<string, unknown>> = Object.entries(data).map(([id, fields]) => { const entry: Record<string, unknown> = { id } Object.assign(entry, fields) return entry }) // Filter if (opts.filter) { entries = entries.filter(entry => { for (const [key, value] of Object.entries(opts.filter!)) { if (entry[key] !== value) return false } return true }) } const total = entries.length // Pagination const offset = opts.offset ?? 0 const limit = opts.limit ?? entries.length entries = entries.slice(offset, offset + limit) // Resolve relations if (opts.resolve && model.fields) { entries = await resolveRelations(projectRoot, model, entries, locale) } return { kind: 'collection', data: entries, total, locale, offset, limit } } case 'document': { const entries: DocumentEntry[] = [] const strategy = resolveLocaleStrategy(model) if (!model.i18n) { // No i18n: flat {slug}.md files, no locale in path - packages/mcp/src/tools/content.ts:252-301 (registration)The registration of the 'contentrain_content_list' MCP tool, which calls listContent.
server.tool( 'contentrain_content_list', 'List content entries (read-only). Returns data from .contentrain/ — do NOT manually create or modify content files.', { model: z.string().describe('Model ID'), locale: z.string().optional().describe('Locale code (defaults to config default)'), filter: z.record(z.string(), z.unknown()).optional().describe('Filter criteria (collection only)'), resolve: z.boolean().optional().describe('Resolve relation fields to actual data'), limit: z.number().optional().describe('Max entries to return'), offset: z.number().optional().describe('Skip N entries'), }, async (input) => { const config = await readConfig(projectRoot) if (!config) { return { content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized.' }) }], isError: true, } } const model = await readModel(projectRoot, input.model) if (!model) { return { content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model "${input.model}" not found` }) }], isError: true, } } try { const result = await listContent(projectRoot, model, { locale: input.locale, filter: input.filter as Record<string, unknown>, resolve: input.resolve, limit: input.limit, offset: input.offset, }, config) return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], } } catch (error) { return { content: [{ type: 'text' as const, text: JSON.stringify({ error: `List failed: ${error instanceof Error ? error.message : String(error)}`, }) }], isError: true, } } }, )