@sellable/mcp 0.1.541 → 0.1.543

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/dist/server.js CHANGED
@@ -38,6 +38,7 @@ import { addOnDemandLeads, createOnDemandCampaign, createOnDemandTable, initOnDe
38
38
  import { upsertRubric } from "./tools/processing.js";
39
39
  import { completeSenderResearch, getPostFindLeadsScoutRegistry, getSourceScoutRegistry, getSubskillAsset, getSubskillPrompt, listSubskillPrompts, searchSubskillPrompts, } from "./tools/prompts.js";
40
40
  import { waitForCampaignTableReady, waitForLeadListReady, } from "./tools/readiness.js";
41
+ import { refreshSenderEngagementCommand } from "./tools/refresh-sender-engagement.js";
41
42
  import { refillSendsV2Command } from "./tools/refill-sends-v2.js";
42
43
  import { executeRefillSendsCommand } from "./tools/refill-sends.js";
43
44
  import { getRefillTargetPlan } from "./tools/refill-target-plan.js";
@@ -239,6 +240,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
239
240
  case "run_scheduler_sweep":
240
241
  result = await runSchedulerSweep(args);
241
242
  break;
243
+ case "refresh_sender_engagement":
244
+ result = await refreshSenderEngagementCommand(args);
245
+ break;
242
246
  case "refill_sends":
243
247
  result = await executeRefillSendsCommand(args);
244
248
  break;
@@ -0,0 +1,50 @@
1
+ export type RefreshSenderEngagementInput = {
2
+ workspaceId?: string;
3
+ mode?: "dry_run" | "apply";
4
+ senderId?: string;
5
+ tableId?: string;
6
+ maxPosts?: number;
7
+ maxEngagerPages?: number;
8
+ dryRunFingerprint?: string;
9
+ };
10
+ export declare const refreshSenderEngagementToolDefinitions: {
11
+ name: string;
12
+ description: string;
13
+ inputSchema: {
14
+ type: string;
15
+ properties: {
16
+ workspaceId: {
17
+ type: string;
18
+ description: string;
19
+ };
20
+ mode: {
21
+ type: string;
22
+ enum: string[];
23
+ description: string;
24
+ };
25
+ senderId: {
26
+ type: string;
27
+ description: string;
28
+ };
29
+ tableId: {
30
+ type: string;
31
+ description: string;
32
+ };
33
+ maxPosts: {
34
+ type: string;
35
+ description: string;
36
+ };
37
+ maxEngagerPages: {
38
+ type: string;
39
+ description: string;
40
+ };
41
+ dryRunFingerprint: {
42
+ type: string;
43
+ description: string;
44
+ };
45
+ };
46
+ required: string[];
47
+ additionalProperties: boolean;
48
+ };
49
+ }[];
50
+ export declare function refreshSenderEngagementCommand(input: RefreshSenderEngagementInput): Promise<unknown>;
@@ -0,0 +1,60 @@
1
+ import { getApi } from "../api.js";
2
+ import { createWorkspaceContext } from "./workspace-context.js";
3
+ export const refreshSenderEngagementToolDefinitions = [
4
+ {
5
+ name: "refresh_sender_engagement",
6
+ description: "Refresh one sender-owned Post Engagers campaign through the product-native CampaignTrackedPost path. Dry-run first returns a human-reviewable receipt; apply requires that dry-run fingerprint and never sends, schedules, approves, prepares messages, launches campaigns, or mutates shared Signal Discovery lanes.",
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: {
10
+ workspaceId: {
11
+ type: "string",
12
+ description: "Explicit request-scoped workspace id.",
13
+ },
14
+ mode: {
15
+ type: "string",
16
+ enum: ["dry_run", "apply"],
17
+ description: "dry_run is read-only and returns an approval packet; apply requires dryRunFingerprint.",
18
+ },
19
+ senderId: {
20
+ type: "string",
21
+ description: "Exactly one Sellable sender id to refresh.",
22
+ },
23
+ tableId: {
24
+ type: "string",
25
+ description: "Optional campaign table id for disambiguating the sender-owned Post Engagers lane.",
26
+ },
27
+ maxPosts: {
28
+ type: "number",
29
+ description: "Maximum tracked/sender-authored posts to refresh.",
30
+ },
31
+ maxEngagerPages: {
32
+ type: "number",
33
+ description: "Maximum provider pages per engager source.",
34
+ },
35
+ dryRunFingerprint: {
36
+ type: "string",
37
+ description: "Required for apply; returned by the matching dry_run receipt.",
38
+ },
39
+ },
40
+ required: ["workspaceId", "mode", "senderId"],
41
+ additionalProperties: false,
42
+ },
43
+ },
44
+ ];
45
+ function compactBody(input) {
46
+ return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
47
+ }
48
+ export async function refreshSenderEngagementCommand(input) {
49
+ const workspace = createWorkspaceContext({
50
+ workspaceId: input.workspaceId,
51
+ executionMode: input.mode === "apply" ? "yolo" : "manual",
52
+ toolName: "refresh_sender_engagement",
53
+ });
54
+ if (!workspace.ok)
55
+ return workspace;
56
+ return getApi().post("/api/v3/mcp/refresh-sender-engagement", compactBody({
57
+ ...input,
58
+ workspaceId: workspace.context.workspaceId,
59
+ }), workspace.context.requestOptions);
60
+ }
@@ -7419,6 +7419,45 @@ export declare const allTools: ({
7419
7419
  };
7420
7420
  required: string[];
7421
7421
  };
7422
+ } | {
7423
+ name: string;
7424
+ description: string;
7425
+ inputSchema: {
7426
+ type: string;
7427
+ properties: {
7428
+ workspaceId: {
7429
+ type: string;
7430
+ description: string;
7431
+ };
7432
+ mode: {
7433
+ type: string;
7434
+ enum: string[];
7435
+ description: string;
7436
+ };
7437
+ senderId: {
7438
+ type: string;
7439
+ description: string;
7440
+ };
7441
+ tableId: {
7442
+ type: string;
7443
+ description: string;
7444
+ };
7445
+ maxPosts: {
7446
+ type: string;
7447
+ description: string;
7448
+ };
7449
+ maxEngagerPages: {
7450
+ type: string;
7451
+ description: string;
7452
+ };
7453
+ dryRunFingerprint: {
7454
+ type: string;
7455
+ description: string;
7456
+ };
7457
+ };
7458
+ required: string[];
7459
+ additionalProperties: boolean;
7460
+ };
7422
7461
  } | {
7423
7462
  name: string;
7424
7463
  description: string;
@@ -34,6 +34,7 @@ import { onDemandToolDefinitions } from "./one-off.js";
34
34
  import { processingToolDefinitions } from "./processing.js";
35
35
  import { promptToolDefinitions } from "./prompts.js";
36
36
  import { readinessToolDefinitions } from "./readiness.js";
37
+ import { refreshSenderEngagementToolDefinitions } from "./refresh-sender-engagement.js";
37
38
  import { refillSendsV2ToolDefinitions } from "./refill-sends-v2.js";
38
39
  import { refillSendsToolDefinitions } from "./refill-sends.js";
39
40
  import { refillTargetPlanToolDefinitions } from "./refill-target-plan.js";
@@ -83,6 +84,7 @@ export const allTools = [
83
84
  ...processingToolDefinitions,
84
85
  ...rubricToolDefinitions,
85
86
  ...readinessToolDefinitions,
87
+ ...refreshSenderEngagementToolDefinitions,
86
88
  ...rowToolDefinitions,
87
89
  ...cellToolDefinitions,
88
90
  ...promptToolDefinitions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.541",
3
+ "version": "0.1.543",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",
@@ -239,6 +239,7 @@
239
239
  "record_engage_proven_search",
240
240
  "refill_sends",
241
241
  "refresh_paid_inmail_credits",
242
+ "refresh_sender_engagement",
242
243
  "revise_message_template_and_rerun",
243
244
  "save_domain_filters",
244
245
  "save_hook_research",
@@ -19,10 +19,9 @@ allowed-tools:
19
19
  - mcp__sellable__get_campaign_table_schema
20
20
  - mcp__sellable__list_tables
21
21
  - mcp__sellable__get_rows_minimal
22
+ - mcp__sellable__refresh_sender_engagement
22
23
  - mcp__sellable__fetch_linkedin_posts
23
- - mcp__sellable__select_promising_posts
24
24
  - mcp__sellable__fetch_post_engagers
25
- - mcp__sellable__add_on_demand_leads
26
25
  ---
27
26
 
28
27
  # Refresh Sender Engagement
@@ -34,7 +33,7 @@ You are a pipeline supply agent. People who engage with a sender's LinkedIn post
34
33
  <inputs>
35
34
  The invoking prompt names the senders ("refresh sender engagement for csreyes92 and thomas"). Resolve each via `list_senders` (match name/handle/LinkedIn URL). With no names given, inspect the active workspace and refresh every connected sender that has an active/paused sender-owned Post Engagers campaign backed by Signal Discovery.
36
35
 
37
- Optional: lookback window (default: posts from the last 30 days), engagement sources (default `both` reactions+comments), target sender names/ids, maximum posts per sender (default 5, hard cap 5 unless the user explicitly asks for more), and target campaign ids/names when the user wants to force a specific campaign.
36
+ Optional: target sender names/ids, `tableId` when the user wants to force a specific campaign table, maximum posts per sender (default 5, hard cap 5 unless the user explicitly asks for more), and maximum engager pages per tracked post.
38
37
  </inputs>
39
38
 
40
39
  <entrypoint>
@@ -53,39 +52,36 @@ suggest `refill-sends`.
53
52
  <objective>
54
53
  For each target sender/campaign:
55
54
 
56
- 1. **Find active Signal Discovery/Post Engagers campaigns first**:
55
+ 1. **Authenticate and resolve workspace**:
57
56
  - Call `get_auth_status` and confirm the active workspace.
58
- - Call `get_campaigns` and, when needed, `list_tables`/`get_campaign` to find candidate campaigns.
59
- - Keep only active or paused campaign-backed sender-owned Post Engagers campaigns. Strong signals include campaign/table names like `<Sender> - Post Engagers`, `sourceProvider:"signal-discovery"`, sender attachment to exactly one sender, and source/list/table readback indicating post engagers.
60
- - Exclude shared Signal Discovery lanes, Shared Cold Fallback lanes, archived/completed campaigns, non-campaign tables, direct/on-demand-only campaigns, and any campaign attached to multiple senders unless the user explicitly selected it and the readback proves it is sender-owned.
61
- - If no matching campaign exists for a sender, report "no active Post Engagers Signal Discovery campaign — create one first" and skip; do not silently create campaigns.
62
- 2. **Resolve the sender and source boundary**:
63
- - Match each candidate campaign to its sender from `list_senders`/`get_sender`.
64
- - Use the sender's own LinkedIn profile URL/handle as the only source author boundary.
65
- - Never scrape third-party authors into a sender-owned Post Engagers campaign.
66
- 3. **Find relevant sender-authored posts to refresh**:
67
- - Call `fetch_linkedin_posts({ linkedinUrl: sender profile, limit: 25 })`.
68
- - Keep original posts authored by that exact sender, not reposts, from the lookback window.
69
- - Rank posts by recency, engagement count, and fit to the campaign's buyer/problem/topic. Prefer posts likely to attract the target buyer over generic company updates.
70
- - If the campaign has an existing Signal Discovery/source state that names tracked/selected posts, prefer refreshing those posts when they are still inside the lookback window and relevant; otherwise choose the strongest recent sender-authored posts.
71
- - When the product flow expects selected posts to be visible, call `select_promising_posts` before scraping.
72
- 4. **Pull latest engagers**:
73
- - For the top posts (up to 5 per sender per run), call `fetch_post_engagers({ postUrl, sources })`.
74
- - Default `sources` to `"both"` unless the user requested reactions-only or comments-only.
75
- 5. **Filter to ICP** using the campaign's existing headline ICP criteria (from `get_campaign` brief/rubrics/table schema). Judge each engager's headline against those criteria; exclude obvious non-fits, the sender's own colleagues, existing employees, students/job-seekers, competitors, and anyone with no usable headline. When the campaign has no criteria, keep likely decision-makers/operators and exclude weak-fit profiles.
76
- 6. **Add net-new leads only**:
77
- - Use `add_on_demand_leads({ tableId, leads, skipDuplicates: true })` with name, headline-derived title, profile URL, and source/post context where the tool accepts it.
78
- - Dedupe is mandatory. Never disable dedupe on a scheduled run.
79
- - Do not generate, approve, schedule, or send messages.
80
- 7. **Report**: campaigns inspected, target campaigns selected, posts scanned, engagers found, ICP-passing, net-new added per sender/campaign. If a sender posted nothing in the window, say "no recent posts — nothing to refresh" (that is a truthful no-op, not a failure).
57
+ - If there is no active workspace or auth is missing, stop with the MCP guidance. Do not use local env files or repo scripts as a substitute for MCP auth.
58
+ - Resolve each target sender with `list_senders`/`get_sender`. Use the sender id returned by the product, not a guessed handle.
59
+ 2. **Use the typed product command first**:
60
+ - Call `refresh_sender_engagement` in `mode:"dry_run"` for each sender.
61
+ - Always pass `workspaceId`, `senderId`, and any user-specified `tableId`, `maxPosts`, or `maxEngagerPages`.
62
+ - Treat the dry-run response as the campaign/source boundary authority. It should identify the sender-owned Post Engagers slot, campaign, workflow table, source provider `campaign-tracked-post`, source table type `tracked_post_engager_source_list`, expected tracked posts, expected engager refresh/import work, and a `dryRunFingerprint`.
63
+ - If the typed command reports no eligible sender-owned Post Engagers campaign, no tracked posts, no recent posts, or a workspace/sender mismatch, report that exact blocker and stop for that sender. Do not silently create campaigns or fall back to shared lanes.
64
+ 3. **Gate writes with dry-run proof**:
65
+ - Never call `refresh_sender_engagement` in `mode:"apply"` before a successful dry run from this same run.
66
+ - For manual runs, show the target workspace, sender, campaign/table, source lead list, expected tracked posts, expected row/import impact, and `dryRunFingerprint`, then wait for explicit user approval before apply.
67
+ - For scheduled automations where the user has already authorized refreshes, apply is allowed only with the exact `dryRunFingerprint` from the immediately preceding dry run.
68
+ - If apply rejects the fingerprint as stale or mismatched, rerun dry-run and ask for approval again for manual runs.
69
+ 4. **Apply through the typed command only**:
70
+ - Call `refresh_sender_engagement({ mode:"apply", workspaceId, senderId, tableId?, maxPosts?, maxEngagerPages?, dryRunFingerprint })`.
71
+ - The apply path must materialize/refresh tracked posts, import deduped tracked-post engagers into the campaign table, and preserve product idempotency. Do not use `add_on_demand_leads` or raw table row writes for the same operation.
72
+ 5. **Read-only fallback is diagnostic only**:
73
+ - If the typed command is unavailable, use `get_subskill_prompt`/`search_subskill_prompts` and stop with `blocked: missing_refresh_sender_engagement_tool`.
74
+ - `fetch_linkedin_posts` and `fetch_post_engagers` may be used only to explain source availability or diagnose provider issues. They are not an alternate write path.
75
+ 6. **Report**: campaigns inspected, target campaign/table, tracked posts materialized/refreshed, engagers scanned, ICP-passing/importable, net-new rows imported, duplicates skipped, and any no-op reason per sender/campaign. If a sender posted nothing in the window, say "no recent posts — nothing to refresh" (that is a truthful no-op, not a failure).
81
76
  </objective>
82
77
 
83
78
  <safety>
84
- - LinkedIn operations here are read-only fetches plus adding rows to a campaign table. **No messages are generated, approved, or sent by this skill.**
79
+ - LinkedIn operations here are read-only fetches plus a typed product import into a campaign table. **No messages are generated, approved, scheduled, or sent by this skill.**
85
80
  - Respect workspace boundaries: only add leads to campaigns in the active workspace, and only for senders that belong to it.
86
81
  - Respect campaign boundaries: only add engagers to the matched sender-owned Post Engagers campaign/table. Do not mix shared-lane engagers into sender-owned campaigns or sender-owned engagers into shared lanes.
87
82
  - Cap provider usage per run: at most 5 posts × `fetch_post_engagers` per sender. If the invoking automation wants more, it must say so explicitly.
88
- - Never call `start_campaign`, `attach_sequence`, `queue_campaign_cells`, `start_campaign_message_preparation`, approval tools, or any send/schedule tool.
83
+ - Never call `start_campaign`, `attach_sequence`, `queue_campaign_cells`, `start_campaign_message_preparation`, approval tools, send/schedule tools, or shared-lane mutation tools.
84
+ - Never create campaigns, change campaign status, change sender ownership, approve campaign cells, or start message prep as part of this refresh.
89
85
  </safety>
90
86
 
91
87
  <output>
@@ -1,9 +0,0 @@
1
- {
2
- "parallelMode": "wide",
3
- "agentCount": 6,
4
- "maxToolCallsPerAgent": 2,
5
- "senderMaxAgents": 2,
6
- "senderMaxToolCallsPerAgent": 3,
7
- "progressMode": true,
8
- "debugMode": true
9
- }