@sendinel/mcp-server 1.0.40 → 1.0.41

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.
@@ -2115,16 +2115,10 @@ Requirements: urgency without desperation, acknowledge the gap, one clear CTA, v
2115
2115
  const projectId = getProjectId();
2116
2116
  const maxGroups = Math.min(limit, 200);
2117
2117
  const CONTACT_SCAN_LIMIT = 2000;
2118
- let q = db()
2119
- .from('subscribers')
2120
- .select('id, email, first_name, last_name, engagement_score, lifecycle_stage, created_at, source', { count: 'exact' })
2121
- .eq('project_id', projectId)
2122
- .eq('global_unsubscribed', false)
2123
- .order('created_at', { ascending: true })
2124
- .limit(CONTACT_SCAN_LIMIT);
2125
- // #3604: hide seed/fixture rows like every other default contact view.
2126
- if (!include_test)
2127
- q = q.not('is_test', 'is', true);
2118
+ // #3604: PostgREST returns at most 1,000 rows per request, so a single
2119
+ // .limit(2000) query only ever read 1,000. Read in pages up to the limit.
2120
+ const SCAN_PAGE = 1000;
2121
+ let siteIds = null;
2128
2122
  if (site_id) {
2129
2123
  const { data: subs } = await db()
2130
2124
  .from('site_subscriptions')
@@ -2134,9 +2128,38 @@ Requirements: urgency without desperation, acknowledge the gap, one clear CTA, v
2134
2128
  const ids = (subs ?? []).map((s) => s.contact_id);
2135
2129
  if (!ids.length)
2136
2130
  return j({ duplicate_groups: [], total_groups: 0, coverage: { contacts_scanned: 0, contacts_matching: 0, contact_limit: CONTACT_SCAN_LIMIT, complete: true } });
2137
- q = q.in('id', ids);
2131
+ siteIds = ids;
2132
+ }
2133
+ const pageQuery = (from) => {
2134
+ let q = db()
2135
+ .from('subscribers')
2136
+ .select('id, email, first_name, last_name, engagement_score, lifecycle_stage, created_at, source', { count: 'exact' })
2137
+ .eq('project_id', projectId)
2138
+ .eq('global_unsubscribed', false);
2139
+ // #3604: hide seed/fixture rows like every other default contact view.
2140
+ if (!include_test)
2141
+ q = q.not('is_test', 'is', true);
2142
+ if (siteIds)
2143
+ q = q.in('id', siteIds);
2144
+ return q.order('created_at', { ascending: true }).order('id', { ascending: true })
2145
+ .range(from, Math.min(from + SCAN_PAGE, CONTACT_SCAN_LIMIT) - 1);
2146
+ };
2147
+ const contacts = [];
2148
+ let count = null;
2149
+ let error = null;
2150
+ for (let from = 0; from < CONTACT_SCAN_LIMIT; from += SCAN_PAGE) {
2151
+ const res = await pageQuery(from);
2152
+ if (res.error) {
2153
+ error = res.error;
2154
+ break;
2155
+ }
2156
+ if (from === 0)
2157
+ count = typeof res.count === 'number' ? res.count : null;
2158
+ const rows = res.data ?? [];
2159
+ contacts.push(...rows);
2160
+ if (rows.length < SCAN_PAGE)
2161
+ break;
2138
2162
  }
2139
- const { data: contacts, error, count } = await q;
2140
2163
  if (error)
2141
2164
  return txt(`Error: ${error.message}`);
2142
2165
  // #3604: say when the scan stopped at the limit instead of presenting a partial scan as complete.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendinel/mcp-server",
3
- "version": "1.0.40",
3
+ "version": "1.0.41",
4
4
  "description": "AI-controlled email operations control plane for campaigns, contacts, segments, analytics, and multi-provider sending via MCP.",
5
5
  "mcpName": "ai.sendinel/mcp-server",
6
6
  "type": "module",