@sellable/mcp 0.1.108 → 0.1.109
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 +3 -1
- package/dist/tools/campaigns.d.ts +2 -0
- package/dist/tools/campaigns.js +6 -11
- package/dist/tools/watch-url-security.d.ts +1 -0
- package/dist/tools/watch-url-security.js +36 -0
- package/package.json +1 -1
- package/skills/create-campaign-v2/references/watch-link-handoff.md +7 -4
package/dist/server.js
CHANGED
|
@@ -29,6 +29,7 @@ import { getSender, listSenders, senderToolDefinitions, } from "./tools/senders.
|
|
|
29
29
|
import { attachRecommendedSequence, attachSequence, createWorkflowTable, sequencerToolDefinitions, } from "./tools/sequencer.js";
|
|
30
30
|
import { listTables, tableToolDefinitions } from "./tools/tables.js";
|
|
31
31
|
import { handleVerifyTableRow, verifyRowToolDefinitions, } from "./tools/verify-row.js";
|
|
32
|
+
import { sanitizeWatchUrlsForMcpResult } from "./tools/watch-url-security.js";
|
|
32
33
|
import { addTeammate, createWorkspace, getActiveWorkspace, listWorkspaces, setActiveWorkspace, workspaceToolDefinitions, } from "./tools/workspaces.js";
|
|
33
34
|
import { checkForUpdates, logUpdateNotice } from "./update-check.js";
|
|
34
35
|
const server = new Server({
|
|
@@ -473,11 +474,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
473
474
|
default:
|
|
474
475
|
throw new Error(`Unknown tool: ${name}`);
|
|
475
476
|
}
|
|
477
|
+
const safeResult = sanitizeWatchUrlsForMcpResult(result);
|
|
476
478
|
return {
|
|
477
479
|
content: [
|
|
478
480
|
{
|
|
479
481
|
type: "text",
|
|
480
|
-
text: JSON.stringify(
|
|
482
|
+
text: JSON.stringify(safeResult),
|
|
481
483
|
},
|
|
482
484
|
],
|
|
483
485
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getConfig } from "../auth.js";
|
|
1
2
|
import { type InteractionMode } from "./interaction-mode.js";
|
|
2
3
|
import type { CampaignOfferNavigation } from "./navigation.js";
|
|
3
4
|
declare const LEAD_SOURCE_PROVIDERS: {
|
|
@@ -7,6 +8,7 @@ declare const LEAD_SOURCE_PROVIDERS: {
|
|
|
7
8
|
readonly SIGNAL_DISCOVERY: "signal-discovery";
|
|
8
9
|
};
|
|
9
10
|
type LeadSourceProvider = (typeof LEAD_SOURCE_PROVIDERS)[keyof typeof LEAD_SOURCE_PROVIDERS];
|
|
11
|
+
export declare function buildWatchUrl(config: Pick<ReturnType<typeof getConfig>, "apiUrl" | "activeWorkspaceId" | "workspaceId">, path: string): string;
|
|
10
12
|
export interface Campaign {
|
|
11
13
|
id: string;
|
|
12
14
|
name: string;
|
package/dist/tools/campaigns.js
CHANGED
|
@@ -19,12 +19,12 @@ function normalizeLeadSourceProvider(input) {
|
|
|
19
19
|
? input
|
|
20
20
|
: null;
|
|
21
21
|
}
|
|
22
|
-
function buildWatchUrl(config,
|
|
22
|
+
export function buildWatchUrl(config, path) {
|
|
23
23
|
const workspaceId = config.activeWorkspaceId || config.workspaceId;
|
|
24
24
|
const workspaceParam = workspaceId
|
|
25
|
-
?
|
|
25
|
+
? `${path.includes("?") ? "&" : "?"}workspaceId=${encodeURIComponent(workspaceId)}`
|
|
26
26
|
: "";
|
|
27
|
-
return `${config.apiUrl}
|
|
27
|
+
return `${config.apiUrl}${path}${workspaceParam}`;
|
|
28
28
|
}
|
|
29
29
|
function isLinkedInProfileUrl(input) {
|
|
30
30
|
try {
|
|
@@ -334,9 +334,7 @@ export async function getCampaign(campaignId) {
|
|
|
334
334
|
api.get(`/api/v3/mcp/campaigns/${campaignId}`),
|
|
335
335
|
fetchCampaignRubrics(campaignId).catch(() => null),
|
|
336
336
|
]);
|
|
337
|
-
|
|
338
|
-
const redirect = encodeURIComponent(`/campaign-builder/${campaignId}?mode=claude`);
|
|
339
|
-
const watchUrl = buildWatchUrl(config, redirect);
|
|
337
|
+
const watchUrl = buildWatchUrl(config, `/campaign-builder/${campaignId}?mode=claude`);
|
|
340
338
|
// Merge rubrics into campaignOffer
|
|
341
339
|
const rubrics = (rubricsResult?.rubrics || []).map((r) => ({
|
|
342
340
|
id: r.id || "",
|
|
@@ -591,8 +589,7 @@ export async function createCampaign(input) {
|
|
|
591
589
|
// Idempotent resume path
|
|
592
590
|
if (input.campaignId) {
|
|
593
591
|
const existing = await api.get(`/api/v2/campaign-offers/${input.campaignId}`);
|
|
594
|
-
const
|
|
595
|
-
const watchUrl = buildWatchUrl(config, redirect);
|
|
592
|
+
const watchUrl = buildWatchUrl(config, `/campaign-builder/${existing.id}?mode=claude`);
|
|
596
593
|
const hasCreateFields = input.name ||
|
|
597
594
|
input.clientProspectId ||
|
|
598
595
|
input.offerPositioning !== undefined ||
|
|
@@ -710,9 +707,7 @@ export async function createCampaign(input) {
|
|
|
710
707
|
},
|
|
711
708
|
};
|
|
712
709
|
const result = await api.post(`/api/v2/campaign-offers`, formattedInput);
|
|
713
|
-
|
|
714
|
-
const redirect = encodeURIComponent(`/campaign-builder/${result.id}?mode=claude`);
|
|
715
|
-
const watchUrl = buildWatchUrl(config, redirect);
|
|
710
|
+
const watchUrl = buildWatchUrl(config, `/campaign-builder/${result.id}?mode=claude`);
|
|
716
711
|
// Serialize to only essential fields for context efficiency
|
|
717
712
|
return {
|
|
718
713
|
id: result.id,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sanitizeWatchUrlsForMcpResult<T>(value: T): T;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
function sanitizeWatchUrlValue(value) {
|
|
2
|
+
try {
|
|
3
|
+
const url = new URL(value);
|
|
4
|
+
if (url.pathname !== "/auth/continue")
|
|
5
|
+
return value;
|
|
6
|
+
const redirect = url.searchParams.get("redirect");
|
|
7
|
+
if (!redirect || !redirect.startsWith("/campaign-builder/")) {
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
const safeUrl = new URL(redirect, url.origin);
|
|
11
|
+
const workspaceId = url.searchParams.get("workspaceId");
|
|
12
|
+
if (workspaceId && !safeUrl.searchParams.has("workspaceId")) {
|
|
13
|
+
safeUrl.searchParams.set("workspaceId", workspaceId);
|
|
14
|
+
}
|
|
15
|
+
return safeUrl.toString();
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function sanitizeWatchUrlsForMcpResult(value) {
|
|
22
|
+
if (!value || typeof value !== "object")
|
|
23
|
+
return value;
|
|
24
|
+
if (Array.isArray(value)) {
|
|
25
|
+
return value.map((item) => sanitizeWatchUrlsForMcpResult(item));
|
|
26
|
+
}
|
|
27
|
+
const input = value;
|
|
28
|
+
const output = {};
|
|
29
|
+
for (const [key, nestedValue] of Object.entries(input)) {
|
|
30
|
+
output[key] =
|
|
31
|
+
key === "watchUrl" && typeof nestedValue === "string"
|
|
32
|
+
? sanitizeWatchUrlValue(nestedValue)
|
|
33
|
+
: sanitizeWatchUrlsForMcpResult(nestedValue);
|
|
34
|
+
}
|
|
35
|
+
return output;
|
|
36
|
+
}
|
package/package.json
CHANGED
|
@@ -11,11 +11,12 @@ gating, and handoff read campaign state first.
|
|
|
11
11
|
|
|
12
12
|
## Plumbing Reuse
|
|
13
13
|
|
|
14
|
-
`create_campaign` already returns a
|
|
14
|
+
`create_campaign` already returns a safe app `watchUrl` on the response
|
|
15
15
|
(`CampaignDetail.watchUrl` in `mcp/sellable/src/tools/campaigns.ts`). V2 does
|
|
16
16
|
NOT mint a new token, does NOT call a different route, and does NOT construct
|
|
17
17
|
the URL locally. Capture whatever `watchUrl` the existing tool returns and
|
|
18
|
-
surface it verbatim.
|
|
18
|
+
surface it verbatim. The URL must never include a raw API token, `auth/continue`
|
|
19
|
+
token exchange URL, magic-link secret, or other bearer credential.
|
|
19
20
|
|
|
20
21
|
## Shell-First Link
|
|
21
22
|
|
|
@@ -80,18 +81,20 @@ Do not spam the link between internal tool calls.
|
|
|
80
81
|
On resume:
|
|
81
82
|
|
|
82
83
|
1. Load the `CampaignOffer` for the campaign being resumed.
|
|
83
|
-
2. Recover the
|
|
84
|
+
2. Recover the safe app link through the existing resume path
|
|
84
85
|
(`create_campaign({ campaignId })` when available).
|
|
85
86
|
3. Inspect `currentStep` and print a short orientation for where the user will
|
|
86
87
|
land now.
|
|
87
88
|
4. Print the recovered `watchUrl`.
|
|
88
89
|
|
|
89
|
-
The re-surface must use the
|
|
90
|
+
The re-surface must use the safe `watchUrl` from the tool response, not a
|
|
90
91
|
cached or reconstructed URL.
|
|
91
92
|
|
|
92
93
|
## Hard Rules
|
|
93
94
|
|
|
94
95
|
- Never fabricate or reconstruct the URL locally.
|
|
96
|
+
- Never print a URL containing `token=`, `/auth/continue`, a magic-link secret,
|
|
97
|
+
or any other bearer credential.
|
|
95
98
|
- Missing `watchUrl` is an error, not a silent skip.
|
|
96
99
|
- The first watch link must be shown only after the v1 brief is on the campaign.
|
|
97
100
|
- A watch link is not approval to import, attach sequence, or start.
|