Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 101 additions & 24 deletions src/tools/entries/searchEntries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,104 @@ import { BaseToolSchema, createToolClient } from '../../utils/tools.js';
import { summarizeData } from '../../utils/summarizer.js';

export const SearchEntriesToolParams = BaseToolSchema.extend({
query: z.object({
content_type: z.string().optional().describe('Filter by content type'),
include: z
.number()
.optional()
.describe('Include this many levels of linked entries'),
select: z
.string()
.optional()
.describe('Comma-separated list of fields to return'),
links_to_entry: z
.string()
.optional()
.describe('Find entries that link to the specified entry ID'),
limit: z
.number()
.optional()
.describe('Maximum number of entries to return'),
skip: z.number().optional().describe('Skip this many entries'),
order: z.string().optional().describe('Order entries by this field'),
}),
query: z
.object({
// Core parameters (maintain backward compatibility)
content_type: z.string().optional().describe('Filter by content type'),
include: z
.number()
.optional()
.describe('Include this many levels of linked entries'),
select: z
.string()
.optional()
.describe('Comma-separated list of fields to return'),
links_to_entry: z
.string()
.optional()
.describe('Find entries that link to the specified entry ID'),
limit: z
.number()
.optional()
.describe(
'Maximum number of entries to return (default: 10, max: 100)',
),
skip: z
.number()
.optional()
.describe('Skip this many entries for pagination'),
order: z.string().optional().describe('Order entries by this field'),

// Full-text search (like ivo version)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just use // Full-text search as the comment here

query: z
.string()
.optional()
.describe('Full-text search across all fields'),

// Common field-based searches (examples - any field is supported via catchall)
'fields.title': z.string().optional().describe('Search by title field'),
'fields.slug': z.string().optional().describe('Search by slug field'),
'fields.internalName': z
.string()
.optional()
.describe('Search by internal name field'),
'fields.text': z
.string()
.optional()
.describe('Search by text field (useful for testimonials)'),
'fields.title[match]': z
.string()
.optional()
.describe('Pattern match on title field'),
'fields.slug[match]': z
.string()
.optional()
.describe('Pattern match on slug field'),
'fields.title[exists]': z
.boolean()
.optional()
.describe('Check if title field exists'),
'fields.slug[exists]': z
.boolean()
.optional()
.describe('Check if slug field exists'),

// System field searches
'sys.id[in]': z
.array(z.string())
.optional()
.describe('Search by multiple entry IDs'),
'sys.contentType.sys.id': z
.string()
.optional()
.describe('Filter by content type ID'),
'sys.createdAt[gte]': z
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For ISO 8601 datetimes, you can use the z.string().datetime() for zod v3, or z.iso.date() for zod v4

V3 https://v3.zod.dev/?id=strings | V4 https://zod.dev/api#iso-dates

.string()
.optional()
.describe('Created after date (ISO format)'),
'sys.createdAt[lte]': z
.string()
.optional()
.describe('Created before date (ISO format)'),
'sys.updatedAt[gte]': z
.string()
.optional()
.describe('Updated after date (ISO format)'),
'sys.updatedAt[lte]': z
.string()
.optional()
.describe('Updated before date (ISO format)'),

// Metadata searches
'metadata.tags.sys.id[in]': z
.array(z.string())
.optional()
.describe('Filter by tag IDs'),
})
.catchall(z.any())
.describe(
'Flexible search parameters supporting ANY Contentful API query parameter. Use fields.* for field searches, sys.* for system fields, and any other Contentful API parameter.',
),
});

type Params = z.infer<typeof SearchEntriesToolParams>;
Expand All @@ -44,13 +121,13 @@ async function tool(args: Params) {
...params,
query: {
...args.query,
limit: Math.min(args.query.limit || 3, 3),
limit: Math.min(args.query.limit || 10, 100), // Allow up to 100 results, default 10
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this to a reusable fn so we don't need to duplicate the code below in the summarize data.

Something like:

function searchLimit(userLimit?: number) {
  return Math.min(userLimit || 10, 100)
}

skip: args.query.skip || 0,
},
});

const summarized = summarizeData(entries, {
maxItems: 3,
maxItems: Math.min(args.query.limit || 10, 100), // Match the query limit
remainingMessage:
'To see more entries, please ask me to retrieve the next page.',
});
Expand All @@ -62,5 +139,5 @@ async function tool(args: Params) {

export const searchEntriesTool = withErrorHandling(
tool,
'Error deleting dataset',
'Error searching entries',
);