filter_parties
Filter your Capsule parties using structured conditions like date ranges, tags, or custom fields. Retrieve newest records by date filter and highest ID.
Instructions
Filter parties by structured conditions (date ranges, tags, fields). Use this — not search_parties — for questions like 'most recent client', 'parties added this week', 'parties tagged VIP'. Capsule's API does not support ad-hoc sort, but for 'most recent X' you can filter by a date field (e.g. {field: 'addedOn', operator: 'is within last', value: 30}) and pick the highest-id row from the result — Capsule IDs are monotonic, so newest id = newest record.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| conditions | Yes | Array of filter conditions. All conditions are ANDed together. To get newest records, use a date condition like {field: 'addedOn', operator: 'is within last', value: 7} and pick the highest-id row from the result (Capsule IDs are monotonic). | |
| embed | No | Comma-separated embeds, e.g. 'tags,fields' | |
| page | No | ||
| perPage | No |
Implementation Reference
- src/tools/filters.ts:89-103 (handler)The handler function for filter_parties. Calls runFilter with entity path 'parties' and returns paginated results.
export const filterPartiesSchema = FilterInputSchema; export async function filterParties(input: FilterInput) { // Common patterns: // "Parties contacted in last N days": // [{field: "lastContactedOn", operator: "is within last", value: N}] // "Parties added in last N days": // [{field: "addedOn", operator: "is within last", value: N}] // "Organisations only": // [{field: "type", operator: "is", value: "organisation"}] // "Tagged X" (by name or id): // [{field: "tag", operator: "is", value: "VIP"}] // "Has at least one tag" (filter out untagged auto-imports): // [{field: "hasTags", operator: "is", value: true}] return runFilter<{ parties: unknown[]; nextPage: number | undefined }>("parties", input); } - src/tools/filters.ts:40-68 (schema)Input schema (FilterInputSchema) shared by all filter tools including filter_parties. Defines 'conditions', 'embed', 'page', 'perPage' with Zod validation.
const FilterConditionSchema = z.object({ field: z .string() .describe( "The Capsule filter-side field name (these differ from response field names — e.g. response.createdAt is filter-side 'addedOn', response.lastContactedAt is filter-side 'lastContactedOn'). Common: 'addedOn' (date created), 'updatedOn' (date last modified), 'lastContactedOn' (parties only), 'name', 'tag', 'owner', 'team', 'type' (parties: person|organisation), 'milestone' (opportunities), 'status' (opp/project: OPEN|CLOSED), 'closedOn' (opp/project), 'expectedCloseOn' (opp/project), 'hasTags', 'hasEmailAddress' (parties), 'isOpen', 'isStale' (opportunities), 'custom:{fieldId}'. Full per-entity list: https://developer.capsulecrm.com/v2/reference/filters", ), operator: z .string() .describe( "The filter operator. Common: 'is', 'is not' (use value=null to test for null), 'contains', 'does not contain', 'is greater than', 'is less than', 'is within last' (date fields, value=integer days), 'is more than' (date fields, value=integer days ago), 'starts with', 'ends with'. Operator validity depends on the field's type.", ), value: z .union([z.string(), z.number(), z.boolean(), z.null()]) .describe( "The value to compare against. For 'is within last' on date fields, pass an integer number of days. For tag filters, pass the tag name (string) or tag id (number). For 'is not' null tests, pass null literally.", ), }); const FilterInputSchema = z.object({ conditions: z .array(FilterConditionSchema) .min(1) .describe( "Array of filter conditions. All conditions are ANDed together. To get newest records, use a date condition like {field: 'addedOn', operator: 'is within last', value: 7} and pick the highest-id row from the result (Capsule IDs are monotonic).", ), embed: z.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION), page: z.number().int().positive().optional().default(1), perPage: z.number().int().min(1).max(100).optional().default(25), }); - src/tools/filters.ts:74-85 (helper)Generic runFilter helper that POSTs filter conditions to Capsule's /<entity>/filters/results endpoint via capsuleSearch and returns paginated data.
async function runFilter<T>(entityPath: string, input: FilterInput): Promise<T> { const { data, nextPage } = await capsuleSearch<T & object>( `/${entityPath}/filters/results`, { filter: { conditions: input.conditions } }, { page: input.page, perPage: input.perPage, embed: input.embed, }, ); return { ...data, nextPage } as T; } - src/server.ts:237-243 (registration)Registration of the 'filter_parties' MCP tool in createCapsuleMcpServer(). Binds the tool name, description, schema, and handler via registerTool().
registerTool( server, "filter_parties", "Filter parties by structured conditions (date ranges, tags, fields). Use this — not search_parties — for questions like 'most recent client', 'parties added this week', 'parties tagged VIP'. Capsule's API does not support ad-hoc sort, but for 'most recent X' you can filter by a date field (e.g. {field: 'addedOn', operator: 'is within last', value: 30}) and pick the highest-id row from the result — Capsule IDs are monotonic, so newest id = newest record.", filterPartiesSchema, filterParties, ); - src/capsule/client.ts:406-425 (helper)capsuleSearch helper - used by runFilter to POST filter conditions to Capsule's API. Handles auth, retries on 429, and parses pagination from Link headers.
export async function capsuleSearch<T>( path: string, body: unknown, params?: QueryParams, ): Promise<PagedResult<T>> { const token = getToken(); const url = buildUrl(path, params); const { res, cleanup } = await doFetch(url, { method: "POST", headers: { ...baseHeaders(token), "Content-Type": "application/json" }, body: JSON.stringify(body), }); try { const data = await handleResponse<T>(res); const nextPage = parseNextPage(res.headers.get("Link")); return { data, nextPage }; } finally { cleanup(); } }