@sendinel/mcp-server 1.0.40 → 1.0.42

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.
@@ -277,7 +277,7 @@ export async function requestApproval(action, params, apiKeyId, channelContext,
277
277
  action,
278
278
  summary,
279
279
  expires_at: expiresAt,
280
- approve_via: 'Open approve_url (works on a phone), Dashboard → Pending Approvals, or Slack /sendinel approve',
280
+ approve_via: 'Open approve_url (works on a phone) or Dashboard → Settings → Pending Approvals',
281
281
  approve_url: approvalPageUrl(data.id),
282
282
  message: `This action requires human approval. A notification has been sent to your dashboard. Approval ID: ${data.id}. Check status with: poll this approval_id, or wait for the webhook callback (event: approval.decided). Token expires in 30 minutes.`,
283
283
  };
@@ -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.42",
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",