@sendinel/mcp-server 1.0.127 → 1.0.129

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.
package/README.md CHANGED
@@ -18,7 +18,7 @@ preserves REST outcomes, and redacts failures. See the
18
18
  [event-triggered campaign guide](../../docs/event-triggered-campaigns.md) for
19
19
  inputs, idempotency, subscription requirements, and conditional step examples.
20
20
 
21
- Sendinel currently exposes 35 tool groups and 342 tools (gateway mode exposes 4 meta-tools, sharing how_to with full mode). These counts are checked against the tool registry in CI, so this table matches what ships.
21
+ Sendinel currently exposes 35 tool groups and 343 tools (gateway mode exposes 4 meta-tools, sharing how_to with full mode). These counts are checked against the tool registry in CI, so this table matches what ships.
22
22
 
23
23
  | Group | Count | Purpose |
24
24
  |-------|-------|---------|
@@ -56,7 +56,7 @@ Sendinel currently exposes 35 tool groups and 342 tools (gateway mode exposes 4
56
56
  | `surveys` | 5 | Survey creation, listing, status, results |
57
57
  | `templates` | 7 | Email template CRUD, version restore, migration translation |
58
58
  | `warmup` | 4 | IP/domain warmup schedule control |
59
- | `webhooks` | 4 | Webhook subscription management |
59
+ | `webhooks` | 5 | Webhook subscription management |
60
60
 
61
61
  ## Install / run
62
62
 
@@ -1,5 +1,6 @@
1
+ import { deliverProjectWebhook } from "@sendinel/domain/sandbox-webhook";
1
2
  import { createHmac, randomUUID } from 'node:crypto';
2
- import { db } from '../db.js';
3
+ import { adminDb, db } from '../db.js';
3
4
  import { getProjectId } from '../project.js';
4
5
  /**
5
6
  * Fire a webhook event to all active subscriptions matching the event type.
@@ -38,7 +39,7 @@ export async function fireWebhookEvent(event, data, projectId) {
38
39
  const signature = createHmac('sha256', sub.secret)
39
40
  .update(body)
40
41
  .digest('hex');
41
- const res = await fetch(sub.url, {
42
+ const res = await deliverProjectWebhook(adminDb(), pid, sub.url, {
42
43
  method: 'POST',
43
44
  headers: {
44
45
  'Content-Type': 'application/json',
@@ -92,7 +92,7 @@ async function readJsonSafe(res) {
92
92
  return res.json().catch(() => ({}));
93
93
  }
94
94
  async function sendViaProvider(provider, credentialsEnc, fromNumber, toNumber, body, webhookUrl, trackingToken) {
95
- await assertProjectAllowsSms(adminDb(), getProjectId());
95
+ await assertProjectAllowsSms(adminDb(), getProjectId(), toNumber);
96
96
  const credentials = parseCredentials(credentialsEnc);
97
97
  const meta = countSmsSegments(body);
98
98
  if (provider === 'twilio') {
@@ -5,7 +5,7 @@ import { db, adminDb } from '../db.js';
5
5
  import { decrypt } from '../crypto.js';
6
6
  import { getProjectId } from '../project.js';
7
7
  import { getAnthropicModelId } from '../anthropic-model.js';
8
- import { AI_MAX_TOKENS, outputConfig, anthropicCheckedText } from '@sendinel/domain';
8
+ import { AI_MAX_TOKENS, outputConfig, anthropicCheckedText, recordSandboxSocialPost, configuredSandboxSocialProfile, assertProjectSandboxTargets } from '@sendinel/domain';
9
9
  import { buildVoicePrompt } from '../lib/voice.js';
10
10
  import { buildSocialSystemPrompt, buildSocialUserPrompt, parseSocialResponse, selectArchetype, } from '../lib/social-engine/index.js';
11
11
  import { fireWebhookEvent } from '../lib/webhook-events.js';
@@ -40,6 +40,14 @@ function buildSocialTrackedUrl(params) {
40
40
  };
41
41
  }
42
42
  async function scheduleUploadPost(apiKey, params) {
43
+ const mock = await recordSandboxSocialPost(adminDb(), {
44
+ projectId: params.projectId, profileId: params.profileId, platforms: [params.platform],
45
+ text: params.link ? `${params.content}\n\n${params.link}` : params.content,
46
+ mediaUrls: params.mediaUrls, scheduledAt: params.scheduledAt,
47
+ });
48
+ if (mock)
49
+ return mock;
50
+ await assertProjectSandboxTargets(adminDb(), params.projectId, 'social', params.profileId ? [`uploadpost:${params.platform}:${params.profileId}`] : []);
43
51
  const res = await fetch(`${UPLOADPOST_API_BASE}/uploadposts/schedule`, {
44
52
  method: 'POST',
45
53
  headers: {
@@ -47,6 +55,7 @@ async function scheduleUploadPost(apiKey, params) {
47
55
  'Content-Type': 'application/json',
48
56
  },
49
57
  body: JSON.stringify({
58
+ user: params.profileId,
50
59
  platform: params.platform,
51
60
  content: params.link ? `${params.content}\n\n${params.link}` : params.content,
52
61
  scheduled_at: params.scheduledAt?.toISOString(),
@@ -711,26 +720,31 @@ export function registerSocialPostTools(server) {
711
720
  return j({ error: 'Campaign not found', code: 'NOT_FOUND' });
712
721
  const { data: project, error: projectError } = await admin
713
722
  .from('projects')
714
- .select('org_id')
723
+ .select('org_id, sandbox_mode, sandbox_allowed_social_profiles')
715
724
  .eq('id', campaign.project_id)
716
725
  .maybeSingle();
717
726
  if (projectError)
718
727
  return txt(`Error: ${projectError.message}`);
719
728
  if (!project?.org_id)
720
729
  return j({ error: 'Project not found', code: 'NOT_FOUND' });
721
- const { data: connection, error: connectionError } = await admin
722
- .from('social_platform_connections')
723
- .select('encrypted_access_token')
724
- .eq('org_id', project.org_id)
725
- .eq('platform', p.publisher)
726
- .maybeSingle();
730
+ const mockProfile = configuredSandboxSocialProfile(project, campaign.platform);
731
+ const { data: connection, error: connectionError } = mockProfile
732
+ ? { data: { encrypted_access_token: null, account_id: mockProfile }, error: null }
733
+ : await admin
734
+ .from('social_platform_connections')
735
+ .select('encrypted_access_token, account_id')
736
+ .eq('org_id', project.org_id)
737
+ .eq('platform', p.publisher)
738
+ .maybeSingle();
727
739
  if (connectionError)
728
740
  return txt(`Error: ${connectionError.message}`);
729
- if (!connection?.encrypted_access_token) {
741
+ if (!mockProfile && !connection?.encrypted_access_token) {
730
742
  return j({ error: `No ${p.publisher} connection found. Connect it in Settings -> Integrations.`, code: 'NO_CONNECTION' });
731
743
  }
732
744
  try {
733
- const result = await publishViaByoPublisher(p.publisher, decrypt(connection.encrypted_access_token), {
745
+ const result = await publishViaByoPublisher(p.publisher, mockProfile ? "" : decrypt(connection.encrypted_access_token), {
746
+ projectId,
747
+ profileId: connection.account_id ?? undefined,
734
748
  content: p.content,
735
749
  platform: campaign.platform,
736
750
  scheduledAt,
@@ -1,6 +1,7 @@
1
1
  import { withApproval } from '../lib/action-approvals.js';
2
2
  import { z } from 'zod';
3
- import { db } from '../db.js';
3
+ import { listSandboxWebhookEvents } from '@sendinel/domain';
4
+ import { db, adminDb } from '../db.js';
4
5
  import { getProjectId } from '../project.js';
5
6
  import { validateWebhookUrl, generateWebhookSecret } from '../lib/webhook-events.js';
6
7
  const txt = (text) => ({ content: [{ type: 'text', text }] });
@@ -25,6 +26,19 @@ const VALID_EVENTS = [
25
26
  'asset_link.viewed',
26
27
  ];
27
28
  export function registerWebhookTools(server) {
29
+ server.registerTool('list_sandbox_webhook_events', {
30
+ title: 'List Sandbox Webhook Events',
31
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
32
+ description: 'Read recent captured webhook requests for the active sandbox project (seven-day retention). Never returns sink tokens.',
33
+ inputSchema: { limit: z.number().int().min(1).max(100).optional().default(20) },
34
+ }, async ({ limit }) => {
35
+ try {
36
+ return j({ events: await listSandboxWebhookEvents(adminDb(), getProjectId(), limit) });
37
+ }
38
+ catch {
39
+ return txt('Sandbox webhook events unavailable.');
40
+ }
41
+ });
28
42
  server.registerTool('subscribe_webhook', {
29
43
  title: 'Subscribe Webhook',
30
44
  annotations: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendinel/mcp-server",
3
- "version": "1.0.127",
3
+ "version": "1.0.129",
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",