@sellable/mcp 0.1.770 → 0.1.771

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.
@@ -1,7 +1,7 @@
1
1
  import { getConfig } from "../auth.js";
2
+ import { type Campaign1KickoffHandoffInput, type Campaign1KickoffHandoffValidationResult } from "../utils/campaign1KickoffHandoff.js";
2
3
  import { type InteractionMode } from "./interaction-mode.js";
3
4
  import type { CampaignOfferNavigation } from "./navigation.js";
4
- import { type Campaign1KickoffHandoffInput, type Campaign1KickoffHandoffValidationResult } from "../utils/campaign1KickoffHandoff.js";
5
5
  declare const LEAD_SOURCE_PROVIDERS: {
6
6
  readonly APOLLO: "apollo-ai";
7
7
  readonly SALES_NAV: "sales-nav";
@@ -10,6 +10,14 @@ declare const LEAD_SOURCE_PROVIDERS: {
10
10
  };
11
11
  type LeadSourceProvider = (typeof LEAD_SOURCE_PROVIDERS)[keyof typeof LEAD_SOURCE_PROVIDERS];
12
12
  export declare function buildWatchUrl(config: Pick<ReturnType<typeof getConfig>, "apiUrl" | "token" | "activeWorkspaceId" | "workspaceId">, path: string): string;
13
+ /**
14
+ * Ask the API to mint a fresh, single-use campaign watch link.
15
+ *
16
+ * The server derives the token from the identity it already resolved, so this
17
+ * works identically for `skt_*` CLI/MCP tokens and `sat_*` agent credentials.
18
+ * Falls back to the locally-built link if the mint call fails.
19
+ */
20
+ export declare function mintCampaignWatchUrl(campaignId: string): Promise<string>;
13
21
  export type CampaignBuilderWatchMode = "claude" | "codex" | "hermes";
14
22
  export declare function getCampaignBuilderWatchModeParam(): CampaignBuilderWatchMode;
15
23
  export declare function getCampaignBuilderWatchModeDriverLabel(mode?: CampaignBuilderWatchMode): "Claude Code" | "Codex" | "Hermes";
@@ -1,10 +1,10 @@
1
1
  import { getApi } from "../api.js";
2
2
  import { getConfig, getEffectiveConfiguredWorkspaceId } from "../auth.js";
3
+ import { parseCampaign1KickoffHandoff, } from "../utils/campaign1KickoffHandoff.js";
3
4
  import { assertCreateCampaignPromptLoaded, assertNetNewCreateCampaignResearchReady, } from "./flow-preflight.js";
4
5
  import { setCampaignInteractionMode, } from "./interaction-mode.js";
5
6
  import { isLinkedInProfileInput, normalizeLinkedInProfileInput, } from "./linkedin-url.js";
6
7
  import { fetchCampaignRubrics } from "./processing.js";
7
- import { parseCampaign1KickoffHandoff, } from "../utils/campaign1KickoffHandoff.js";
8
8
  const LEAD_SOURCE_PROVIDERS = {
9
9
  APOLLO: "apollo-ai",
10
10
  SALES_NAV: "sales-nav",
@@ -96,7 +96,13 @@ const WATCH_NARRATION_TOOL_SCHEMA = {
96
96
  messageDraftOutput: { type: ["object", "null"] },
97
97
  error: { type: ["string", "null"] },
98
98
  },
99
- required: ["statusSource", "status", "startedAt", "updatedAt", "basis"],
99
+ required: [
100
+ "statusSource",
101
+ "status",
102
+ "startedAt",
103
+ "updatedAt",
104
+ "basis",
105
+ ],
100
106
  },
101
107
  },
102
108
  additionalProperties: false,
@@ -120,11 +126,40 @@ export function buildWatchUrl(config, path) {
120
126
  if (workspaceId && !url.searchParams.has("workspaceId")) {
121
127
  url.searchParams.set("workspaceId", workspaceId);
122
128
  }
123
- if (config.token && !url.searchParams.has("token")) {
129
+ // Only `skt_*` API tokens can be exchanged at `/auth/continue`. Sellable Agent
130
+ // callers hold `sat_*` service credentials, which live in a different table
131
+ // entirely — attaching one here produces a link that always fails with
132
+ // "Invalid token format", so leave the link token-less and let the caller mint
133
+ // a real watch token via `mintCampaignWatchUrl`.
134
+ if (config.token &&
135
+ config.token.startsWith("skt_") &&
136
+ !url.searchParams.has("token")) {
124
137
  url.searchParams.set("token", config.token);
125
138
  }
126
139
  return url.toString();
127
140
  }
141
+ /**
142
+ * Ask the API to mint a fresh, single-use campaign watch link.
143
+ *
144
+ * The server derives the token from the identity it already resolved, so this
145
+ * works identically for `skt_*` CLI/MCP tokens and `sat_*` agent credentials.
146
+ * Falls back to the locally-built link if the mint call fails.
147
+ */
148
+ export async function mintCampaignWatchUrl(campaignId) {
149
+ const config = getConfig();
150
+ const fallback = buildWatchUrl(config, buildCampaignBuilderWatchPath(campaignId));
151
+ try {
152
+ const result = await getApi().get(`/api/v3/mcp/campaigns/${campaignId}?mode=${getCampaignBuilderWatchModeParam()}`);
153
+ if (result?.watchUrl &&
154
+ isValidBriefHandoffWatchUrl(result.watchUrl, campaignId)) {
155
+ return result.watchUrl;
156
+ }
157
+ }
158
+ catch {
159
+ // Fall through to the locally-built link.
160
+ }
161
+ return fallback;
162
+ }
128
163
  const CAMPAIGN_BUILDER_AGENT_WATCH_MODES = new Set([
129
164
  "claude",
130
165
  "codex",
@@ -600,10 +635,15 @@ export async function getCampaign(campaignId) {
600
635
  const config = getConfig();
601
636
  // Fetch campaign context and rubrics in parallel
602
637
  const [result, rubricsResult] = await Promise.all([
603
- api.get(`/api/v3/mcp/campaigns/${campaignId}`),
638
+ api.get(`/api/v3/mcp/campaigns/${campaignId}?mode=${getCampaignBuilderWatchModeParam()}`),
604
639
  fetchCampaignRubrics(campaignId).catch(() => null),
605
640
  ]);
606
- const watchUrl = buildWatchUrl(config, buildCampaignBuilderWatchPath(campaignId));
641
+ // Prefer the server-minted watch link. It carries a fresh single-use token
642
+ // that works for both `skt_*` and `sat_*` callers; the local builder is only a
643
+ // fallback for older deployments that do not mint one.
644
+ const watchUrl = result.watchUrl && isValidBriefHandoffWatchUrl(result.watchUrl, campaignId)
645
+ ? result.watchUrl
646
+ : buildWatchUrl(config, buildCampaignBuilderWatchPath(campaignId));
607
647
  // Merge rubrics into campaignOffer
608
648
  const rubrics = (rubricsResult?.rubrics || []).map((r) => ({
609
649
  id: r.id || "",
@@ -890,11 +930,10 @@ function buildResumeNextStep() {
890
930
  }
891
931
  export async function createCampaign(input) {
892
932
  const api = getApi();
893
- const config = getConfig();
894
933
  // Idempotent resume path
895
934
  if (input.campaignId) {
896
935
  const existing = await api.get(`/api/v2/campaign-offers/${input.campaignId}`);
897
- const watchUrl = buildWatchUrl(config, buildCampaignBuilderWatchPath(existing.id));
936
+ const watchUrl = await mintCampaignWatchUrl(existing.id);
898
937
  const hasCreateFields = input.name ||
899
938
  input.clientProspectId ||
900
939
  input.offerPositioning !== undefined ||
@@ -1020,7 +1059,7 @@ export async function createCampaign(input) {
1020
1059
  },
1021
1060
  };
1022
1061
  const result = await api.post(`/api/v2/campaign-offers`, formattedInput);
1023
- const watchUrl = buildWatchUrl(config, buildCampaignBuilderWatchPath(result.id));
1062
+ const watchUrl = await mintCampaignWatchUrl(result.id);
1024
1063
  // Serialize to only essential fields for context efficiency
1025
1064
  return {
1026
1065
  id: result.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.770",
3
+ "version": "0.1.771",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",