@affset/mcp 0.1.1 → 0.3.0
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/.env.example +5 -0
- package/README.md +191 -56
- package/dist/client.d.ts +43 -0
- package/dist/client.js +15 -2
- package/dist/config.d.ts +7 -0
- package/dist/config.js +15 -54
- package/dist/core.d.ts +18 -0
- package/dist/core.js +18 -0
- package/dist/docs.d.ts +39 -0
- package/dist/docs.js +105 -0
- package/dist/index.d.ts +2 -0
- package/dist/lib/format.d.ts +46 -0
- package/dist/lib/format.js +3 -0
- package/dist/lib/integrationUrls.d.ts +87 -0
- package/dist/lib/integrationUrls.js +84 -4
- package/dist/lib/linkArgs.d.ts +32 -0
- package/dist/lib/linkArgs.js +14 -3
- package/dist/lib/patch.d.ts +10 -0
- package/dist/lib/payoutRules.d.ts +38 -0
- package/dist/lib/readBody.d.ts +17 -0
- package/dist/lib/readBody.js +69 -0
- package/dist/lib/targeting.d.ts +51 -0
- package/dist/lib/time.d.ts +38 -0
- package/dist/lib/toolResult.d.ts +7 -0
- package/dist/lib/urls.d.ts +2 -0
- package/dist/lib/zones.d.ts +24 -0
- package/dist/lib/zones.js +9 -1
- package/dist/registerTools.d.ts +40 -0
- package/dist/registerTools.js +437 -0
- package/dist/runtimeConfig.d.ts +37 -0
- package/dist/runtimeConfig.js +93 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +3 -318
- package/dist/tools/createCampaign.d.ts +25 -0
- package/dist/tools/createCampaign.js +20 -8
- package/dist/tools/createTeamMember.d.ts +26 -0
- package/dist/tools/createTrafficSource.d.ts +25 -0
- package/dist/tools/createTrafficSource.js +191 -0
- package/dist/tools/createZone.d.ts +24 -0
- package/dist/tools/createZone.js +13 -1
- package/dist/tools/cutZones.d.ts +29 -0
- package/dist/tools/deletePayoutRule.d.ts +16 -0
- package/dist/tools/getCampaign.d.ts +12 -0
- package/dist/tools/getCampaign.js +150 -0
- package/dist/tools/getStats.d.ts +42 -0
- package/dist/tools/getStats.js +54 -3
- package/dist/tools/getTrackingLink.d.ts +23 -0
- package/dist/tools/getTrackingLink.js +26 -9
- package/dist/tools/getZoneUrl.d.ts +21 -0
- package/dist/tools/getZoneUrl.js +30 -13
- package/dist/tools/listCampaigns.d.ts +23 -0
- package/dist/tools/listConversions.d.ts +31 -0
- package/dist/tools/listConversions.js +29 -10
- package/dist/tools/listPayoutRules.d.ts +12 -0
- package/dist/tools/listSubLabels.d.ts +5 -0
- package/dist/tools/listTargetingRules.d.ts +12 -0
- package/dist/tools/listTargetingTypes.d.ts +5 -0
- package/dist/tools/listTeam.d.ts +14 -0
- package/dist/tools/listTrafficSources.d.ts +17 -0
- package/dist/tools/listTrafficSources.js +63 -0
- package/dist/tools/listZones.d.ts +23 -0
- package/dist/tools/listZones.js +10 -5
- package/dist/tools/removeTargetingRule.d.ts +21 -0
- package/dist/tools/setCampaignStatus.d.ts +18 -0
- package/dist/tools/setPayoutGoal.d.ts +16 -0
- package/dist/tools/setPayoutRule.d.ts +18 -0
- package/dist/tools/setSubLabels.d.ts +22 -0
- package/dist/tools/setTargetingRule.d.ts +21 -0
- package/dist/tools/updateCampaign.d.ts +35 -0
- package/dist/tools/updateTrafficSource.d.ts +25 -0
- package/dist/tools/updateTrafficSource.js +171 -0
- package/dist/tools/updateZone.d.ts +27 -0
- package/dist/tools/updateZone.js +13 -3
- package/dist/tools/whoami.d.ts +6 -0
- package/dist/types.d.ts +257 -0
- package/dist/types.js +2 -0
- package/dist/version.d.ts +7 -0
- package/dist/version.js +8 -0
- package/package.json +15 -2
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { type AffsetClient } from "../client.js";
|
|
4
|
+
import { type TargetingMethod } from "../lib/targeting.js";
|
|
5
|
+
export declare const SET_TARGETING_RULE_DESCRIPTION: string;
|
|
6
|
+
export declare const setTargetingRuleInputSchema: {
|
|
7
|
+
campaign_id: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
|
|
8
|
+
type: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
|
|
9
|
+
method: z.ZodEnum<["whitelist", "blacklist"]>;
|
|
10
|
+
rule: z.ZodString;
|
|
11
|
+
confirm: z.ZodDefault<z.ZodBoolean>;
|
|
12
|
+
};
|
|
13
|
+
type SetTargetingRuleArgs = {
|
|
14
|
+
campaign_id: string | number;
|
|
15
|
+
type: string | number;
|
|
16
|
+
method: TargetingMethod;
|
|
17
|
+
rule: string;
|
|
18
|
+
confirm: boolean;
|
|
19
|
+
};
|
|
20
|
+
export declare function setTargetingRule(client: AffsetClient, args: SetTargetingRuleArgs): Promise<CallToolResult>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { type AffsetClient } from "../client.js";
|
|
4
|
+
import { CAMPAIGN_STATUSES, PACING_VALUES, PAYMENT_MODELS } from "../types.js";
|
|
5
|
+
export declare const UPDATE_CAMPAIGN_DESCRIPTION: string;
|
|
6
|
+
export declare const updateCampaignInputSchema: {
|
|
7
|
+
campaign_id: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
|
|
8
|
+
name: z.ZodOptional<z.ZodString>;
|
|
9
|
+
redirect_url: z.ZodOptional<z.ZodString>;
|
|
10
|
+
status: z.ZodOptional<z.ZodEnum<["active", "paused", "archived"]>>;
|
|
11
|
+
payment_model: z.ZodOptional<z.ZodEnum<["cpa", "cpm"]>>;
|
|
12
|
+
rate: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
start_date: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodNull]>>;
|
|
14
|
+
end_date: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodNull]>>;
|
|
15
|
+
daily_budget: z.ZodOptional<z.ZodUnion<[z.ZodNumber, z.ZodNull]>>;
|
|
16
|
+
total_budget: z.ZodOptional<z.ZodUnion<[z.ZodNumber, z.ZodNull]>>;
|
|
17
|
+
pacing: z.ZodOptional<z.ZodEnum<["asap", "even"]>>;
|
|
18
|
+
confirm: z.ZodDefault<z.ZodBoolean>;
|
|
19
|
+
};
|
|
20
|
+
type UpdateCampaignArgs = {
|
|
21
|
+
campaign_id: string | number;
|
|
22
|
+
name?: string;
|
|
23
|
+
redirect_url?: string;
|
|
24
|
+
status?: (typeof CAMPAIGN_STATUSES)[number];
|
|
25
|
+
payment_model?: (typeof PAYMENT_MODELS)[number];
|
|
26
|
+
rate?: number;
|
|
27
|
+
start_date?: string | number | null;
|
|
28
|
+
end_date?: string | number | null;
|
|
29
|
+
daily_budget?: number | null;
|
|
30
|
+
total_budget?: number | null;
|
|
31
|
+
pacing?: (typeof PACING_VALUES)[number];
|
|
32
|
+
confirm: boolean;
|
|
33
|
+
};
|
|
34
|
+
export declare function updateCampaign(client: AffsetClient, args: UpdateCampaignArgs): Promise<CallToolResult>;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { type AffsetClient } from "../client.js";
|
|
4
|
+
import { TRAFFIC_SOURCE_STATUSES } from "../types.js";
|
|
5
|
+
export declare const UPDATE_TRAFFIC_SOURCE_DESCRIPTION: string;
|
|
6
|
+
export declare const updateTrafficSourceInputSchema: {
|
|
7
|
+
traffic_source_id: z.ZodString;
|
|
8
|
+
name: z.ZodOptional<z.ZodString>;
|
|
9
|
+
tracking_template: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNull]>>;
|
|
10
|
+
postback_template: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNull]>>;
|
|
11
|
+
api_token: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNull]>>;
|
|
12
|
+
status: z.ZodOptional<z.ZodEnum<["active", "archived"]>>;
|
|
13
|
+
confirm: z.ZodDefault<z.ZodBoolean>;
|
|
14
|
+
};
|
|
15
|
+
type UpdateTrafficSourceArgs = {
|
|
16
|
+
traffic_source_id: string;
|
|
17
|
+
name?: string;
|
|
18
|
+
tracking_template?: string | null;
|
|
19
|
+
postback_template?: string | null;
|
|
20
|
+
api_token?: string | null;
|
|
21
|
+
status?: (typeof TRAFFIC_SOURCE_STATUSES)[number];
|
|
22
|
+
confirm: boolean;
|
|
23
|
+
};
|
|
24
|
+
export declare function updateTrafficSource(client: AffsetClient, args: UpdateTrafficSourceArgs): Promise<CallToolResult>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { AffsetApiError } from "../client.js";
|
|
3
|
+
import { SOURCE_CLICK_ID_PARAM, templateHasParam } from "../lib/integrationUrls.js";
|
|
4
|
+
import { displayValue, renderDiff } from "../lib/patch.js";
|
|
5
|
+
import { errorResult, textError, textResult } from "../lib/toolResult.js";
|
|
6
|
+
import { httpUrlError } from "../lib/urls.js";
|
|
7
|
+
import { TRAFFIC_SOURCE_STATUSES, } from "../types.js";
|
|
8
|
+
const NAME_MAX = 200;
|
|
9
|
+
const TEMPLATE_MAX = 2000;
|
|
10
|
+
const TOKEN_MAX = 500;
|
|
11
|
+
/** Zod: replacement string, or null to clear. */
|
|
12
|
+
const clearableTemplate = z.union([z.string().max(TEMPLATE_MAX), z.null()]);
|
|
13
|
+
export const UPDATE_TRAFFIC_SOURCE_DESCRIPTION = "Update a traffic source (name, tracking_template, postback_template, api_token, " +
|
|
14
|
+
'status). Partial update — only provided fields change; pass null (or "") to clear a ' +
|
|
15
|
+
"template or the stored api_token. Zones linked to the source pick the new tracking " +
|
|
16
|
+
"template up immediately in get_zone_url/get_tracking_link. " +
|
|
17
|
+
"DRY-RUN by default; pass confirm=true to apply.";
|
|
18
|
+
export const updateTrafficSourceInputSchema = {
|
|
19
|
+
traffic_source_id: z.string().trim().min(1).describe("Traffic source id to update."),
|
|
20
|
+
name: z
|
|
21
|
+
.string()
|
|
22
|
+
.trim()
|
|
23
|
+
.min(1)
|
|
24
|
+
.max(NAME_MAX)
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("New display name (unique per tenant)."),
|
|
27
|
+
tracking_template: clearableTemplate
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("New tracking template (query string appended to the zone URL; must not start " +
|
|
30
|
+
"with ? or &), or null to clear it."),
|
|
31
|
+
postback_template: clearableTemplate
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("New postback template (http(s) URL with our macros), or null to clear it."),
|
|
34
|
+
api_token: z
|
|
35
|
+
.union([z.string().max(TOKEN_MAX), z.null()])
|
|
36
|
+
.optional()
|
|
37
|
+
.describe('Replacement network API credential (stored write-only), or null/"" to clear the ' +
|
|
38
|
+
"stored one. Omit to leave it unchanged."),
|
|
39
|
+
status: z
|
|
40
|
+
.enum(TRAFFIC_SOURCE_STATUSES)
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("active or archived. Archiving keeps the source linked to its zones — deletion is " +
|
|
43
|
+
"only possible once no zone references it."),
|
|
44
|
+
confirm: z
|
|
45
|
+
.boolean()
|
|
46
|
+
.default(false)
|
|
47
|
+
.describe("false = dry-run preview (default). true = apply the update."),
|
|
48
|
+
};
|
|
49
|
+
export async function updateTrafficSource(client, args) {
|
|
50
|
+
try {
|
|
51
|
+
const patch = buildPatch(args);
|
|
52
|
+
if ("error" in patch)
|
|
53
|
+
return textError(patch.error);
|
|
54
|
+
if (Object.keys(patch.body).length === 0) {
|
|
55
|
+
return textError("Provide at least one field to update: name, tracking_template, " +
|
|
56
|
+
"postback_template, api_token, status.");
|
|
57
|
+
}
|
|
58
|
+
let existing;
|
|
59
|
+
try {
|
|
60
|
+
existing = await client.get(`/api/traffic-sources/${encodeURIComponent(args.traffic_source_id)}`);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
if (err instanceof AffsetApiError && err.status === 404) {
|
|
64
|
+
return textError(`Traffic source \`${args.traffic_source_id}\` not found in this namespace.`);
|
|
65
|
+
}
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
const changes = diffSource(existing, patch.body);
|
|
69
|
+
if (changes.length === 0) {
|
|
70
|
+
return textResult(`Nothing to change on traffic source \`${existing.id}\` (${existing.name}) — ` +
|
|
71
|
+
"provided fields already match current values.");
|
|
72
|
+
}
|
|
73
|
+
const warnings = [];
|
|
74
|
+
const linkedZones = existing.linked_zones ?? 0;
|
|
75
|
+
if ("tracking_template" in patch.body && linkedZones > 0) {
|
|
76
|
+
warnings.push(`The new tracking template applies to the ${linkedZones} linked ` +
|
|
77
|
+
`zone${linkedZones === 1 ? "" : "s"} the next time a URL is rendered — links ` +
|
|
78
|
+
"already pasted into network campaigns keep their old parameters until replaced.");
|
|
79
|
+
}
|
|
80
|
+
if (typeof patch.body.tracking_template === "string" &&
|
|
81
|
+
patch.body.tracking_template !== "" &&
|
|
82
|
+
!templateHasParam(patch.body.tracking_template, SOURCE_CLICK_ID_PARAM)) {
|
|
83
|
+
warnings.push("⚠️ The new tracking template has no `source_click_id=` parameter — the " +
|
|
84
|
+
"network's click token will not be captured on linked zones' URLs.");
|
|
85
|
+
}
|
|
86
|
+
if (patch.body.status === "archived" && existing.status !== "archived") {
|
|
87
|
+
warnings.push("Archived sources stay linked to their zones and keep rendering their template; " +
|
|
88
|
+
"archiving only hides the source from active pickers.");
|
|
89
|
+
}
|
|
90
|
+
if (!args.confirm) {
|
|
91
|
+
return textResult([
|
|
92
|
+
`**Dry run** — would update traffic source \`${existing.id}\` (${existing.name}).`,
|
|
93
|
+
"",
|
|
94
|
+
renderDiff(changes),
|
|
95
|
+
...(warnings.length ? ["", ...warnings] : []),
|
|
96
|
+
"",
|
|
97
|
+
"Call again with `confirm: true` to apply.",
|
|
98
|
+
].join("\n"));
|
|
99
|
+
}
|
|
100
|
+
const updated = await client.put(`/api/traffic-sources/${encodeURIComponent(args.traffic_source_id)}`, patch.body);
|
|
101
|
+
return textResult([
|
|
102
|
+
`✅ Traffic source \`${updated.id}\` updated.`,
|
|
103
|
+
"",
|
|
104
|
+
renderDiff(changes),
|
|
105
|
+
...(warnings.length ? ["", ...warnings] : []),
|
|
106
|
+
].join("\n"));
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
return errorResult(err);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function buildPatch(args) {
|
|
113
|
+
const body = {};
|
|
114
|
+
if (args.name !== undefined)
|
|
115
|
+
body.name = args.name;
|
|
116
|
+
if (args.status !== undefined)
|
|
117
|
+
body.status = args.status;
|
|
118
|
+
// Templates clear with "" — the server rejects null for them (api_token is the
|
|
119
|
+
// one field where null also clears).
|
|
120
|
+
if (args.tracking_template !== undefined) {
|
|
121
|
+
const value = args.tracking_template === null ? "" : args.tracking_template.trim();
|
|
122
|
+
if (value && /^[?&]/.test(value)) {
|
|
123
|
+
return {
|
|
124
|
+
error: "tracking_template must not start with ? or & — it is appended after ? automatically.",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
body.tracking_template = value;
|
|
128
|
+
}
|
|
129
|
+
if (args.postback_template !== undefined) {
|
|
130
|
+
const value = args.postback_template === null ? "" : args.postback_template.trim();
|
|
131
|
+
if (value) {
|
|
132
|
+
const err = httpUrlError(value, "postback_template");
|
|
133
|
+
if (err)
|
|
134
|
+
return { error: err };
|
|
135
|
+
}
|
|
136
|
+
body.postback_template = value;
|
|
137
|
+
}
|
|
138
|
+
if (args.api_token !== undefined) {
|
|
139
|
+
body.api_token = args.api_token === null ? null : args.api_token.trim();
|
|
140
|
+
}
|
|
141
|
+
return { body };
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* api_token never round-trips: the existing row only carries has_api_token, and
|
|
145
|
+
* the diff must not echo the new value either.
|
|
146
|
+
*/
|
|
147
|
+
function diffSource(existing, body) {
|
|
148
|
+
const changes = [];
|
|
149
|
+
for (const [field, to] of Object.entries(body)) {
|
|
150
|
+
if (field === "api_token") {
|
|
151
|
+
const from = existing.has_api_token ? "(set)" : "(none)";
|
|
152
|
+
const toDisplay = to === null || to === "" ? "(cleared)" : "(set — write-only)";
|
|
153
|
+
if (existing.has_api_token && (to === null || to === "")) {
|
|
154
|
+
changes.push({ field, from, to: toDisplay });
|
|
155
|
+
}
|
|
156
|
+
else if (to !== null && to !== "") {
|
|
157
|
+
changes.push({ field, from, to: toDisplay });
|
|
158
|
+
}
|
|
159
|
+
else if (!existing.has_api_token && (to === null || to === "")) {
|
|
160
|
+
// Clearing an absent token is a no-op; skip so "nothing to change" stays honest.
|
|
161
|
+
}
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const from = existing[field];
|
|
165
|
+
if (String(from ?? "") === String(to ?? ""))
|
|
166
|
+
continue;
|
|
167
|
+
changes.push({ field, from: displayValue(from ?? null), to: displayValue(to) });
|
|
168
|
+
}
|
|
169
|
+
return changes;
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=updateTrafficSource.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { type AffsetClient } from "../client.js";
|
|
4
|
+
import { ZONE_STATUSES } from "../types.js";
|
|
5
|
+
export declare const UPDATE_ZONE_DESCRIPTION: string;
|
|
6
|
+
export declare const updateZoneInputSchema: {
|
|
7
|
+
zone_id: z.ZodString;
|
|
8
|
+
name: z.ZodOptional<z.ZodString>;
|
|
9
|
+
status: z.ZodOptional<z.ZodEnum<["active", "inactive"]>>;
|
|
10
|
+
postback_url: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNull]>>;
|
|
11
|
+
site_url: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNull]>>;
|
|
12
|
+
traffic_back_url: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNull]>>;
|
|
13
|
+
traffic_source_id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNull]>>;
|
|
14
|
+
confirm: z.ZodDefault<z.ZodBoolean>;
|
|
15
|
+
};
|
|
16
|
+
type UpdateZoneArgs = {
|
|
17
|
+
zone_id: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
status?: (typeof ZONE_STATUSES)[number];
|
|
20
|
+
postback_url?: string | null;
|
|
21
|
+
site_url?: string | null;
|
|
22
|
+
traffic_back_url?: string | null;
|
|
23
|
+
traffic_source_id?: string | null;
|
|
24
|
+
confirm: boolean;
|
|
25
|
+
};
|
|
26
|
+
export declare function updateZone(client: AffsetClient, args: UpdateZoneArgs): Promise<CallToolResult>;
|
|
27
|
+
export {};
|
package/dist/tools/updateZone.js
CHANGED
|
@@ -6,8 +6,10 @@ import { httpUrlError } from "../lib/urls.js";
|
|
|
6
6
|
import { ZONE_STATUSES } from "../types.js";
|
|
7
7
|
/** Zod: optional http(s) URL, or null to clear. */
|
|
8
8
|
const clearableUrl = z.union([z.string().min(1), z.null()]);
|
|
9
|
-
export const UPDATE_ZONE_DESCRIPTION = "Update a traffic-source zone (name, status, postback_url, site_url, traffic_back_url
|
|
10
|
-
"Partial update — only provided fields change. Pass null for a URL
|
|
9
|
+
export const UPDATE_ZONE_DESCRIPTION = "Update a traffic-source zone (name, status, postback_url, site_url, traffic_back_url, " +
|
|
10
|
+
"traffic_source_id). Partial update — only provided fields change. Pass null for a URL " +
|
|
11
|
+
"field or traffic_source_id to clear it. Linking a traffic source makes " +
|
|
12
|
+
"get_zone_url/get_tracking_link render its tracking template. " +
|
|
11
13
|
"DRY-RUN by default; pass confirm=true to apply.";
|
|
12
14
|
export const updateZoneInputSchema = {
|
|
13
15
|
zone_id: z.string().min(1).describe("Zone id to update."),
|
|
@@ -18,6 +20,11 @@ export const updateZoneInputSchema = {
|
|
|
18
20
|
.describe("S2S postback URL, or null to clear. Prefer including {source_click_id}."),
|
|
19
21
|
site_url: clearableUrl.optional().describe("Site URL, or null to clear."),
|
|
20
22
|
traffic_back_url: clearableUrl.optional().describe("Traffic-back URL, or null to clear."),
|
|
23
|
+
traffic_source_id: z
|
|
24
|
+
.union([z.string().trim().min(1), z.null()])
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Traffic source to link (see list_traffic_sources; must be in this namespace), " +
|
|
27
|
+
"or null to unlink."),
|
|
21
28
|
confirm: z
|
|
22
29
|
.boolean()
|
|
23
30
|
.default(false)
|
|
@@ -29,7 +36,8 @@ export async function updateZone(client, args) {
|
|
|
29
36
|
if ("error" in patch)
|
|
30
37
|
return textError(patch.error);
|
|
31
38
|
if (Object.keys(patch.body).length === 0) {
|
|
32
|
-
return textError("Provide at least one field to update: name, status, postback_url, site_url,
|
|
39
|
+
return textError("Provide at least one field to update: name, status, postback_url, site_url, " +
|
|
40
|
+
"traffic_back_url, traffic_source_id.");
|
|
33
41
|
}
|
|
34
42
|
let existing;
|
|
35
43
|
try {
|
|
@@ -103,6 +111,8 @@ function buildPatch(args) {
|
|
|
103
111
|
return { error: err };
|
|
104
112
|
body[key] = args[key];
|
|
105
113
|
}
|
|
114
|
+
if (args.traffic_source_id !== undefined)
|
|
115
|
+
body.traffic_source_id = args.traffic_source_id;
|
|
106
116
|
return { body };
|
|
107
117
|
}
|
|
108
118
|
function diffZone(existing, body) {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import type { AffsetClient } from "../client.js";
|
|
3
|
+
import type { Config } from "../runtimeConfig.js";
|
|
4
|
+
export declare const WHOAMI_DESCRIPTION: string;
|
|
5
|
+
export declare const whoamiInputSchema: {};
|
|
6
|
+
export declare function whoami(client: AffsetClient, config: Config): Promise<CallToolResult>;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/** Shapes of the affset tenant API responses this server consumes. */
|
|
2
|
+
/** A single grouped row from `GET /api/stats`. */
|
|
3
|
+
export interface StatRow {
|
|
4
|
+
date?: string;
|
|
5
|
+
campaign_id?: string;
|
|
6
|
+
campaign_name?: string | null;
|
|
7
|
+
zone_id?: string;
|
|
8
|
+
zone_name?: string | null;
|
|
9
|
+
country?: string | null;
|
|
10
|
+
conversion_type?: string | null;
|
|
11
|
+
publisher_email?: string | null;
|
|
12
|
+
advertiser_email?: string | null;
|
|
13
|
+
sub1?: string | null;
|
|
14
|
+
sub2?: string | null;
|
|
15
|
+
sub3?: string | null;
|
|
16
|
+
sub4?: string | null;
|
|
17
|
+
sub5?: string | null;
|
|
18
|
+
impressions: number;
|
|
19
|
+
fallbacks: number;
|
|
20
|
+
unsold: number;
|
|
21
|
+
clicks: number;
|
|
22
|
+
conversions: number;
|
|
23
|
+
spend?: number;
|
|
24
|
+
payout?: number;
|
|
25
|
+
media_cost?: number;
|
|
26
|
+
/** (payout - media_cost) / media_cost; null when the slice carries no cost. */
|
|
27
|
+
roi?: number | null;
|
|
28
|
+
}
|
|
29
|
+
export interface StatsResponse {
|
|
30
|
+
stats: StatRow[];
|
|
31
|
+
period: {
|
|
32
|
+
from: number;
|
|
33
|
+
to: number;
|
|
34
|
+
};
|
|
35
|
+
/** Tenant display names for sub1..sub5 (e.g. sub1 -> "Zone"); {} when none set. */
|
|
36
|
+
sub_labels?: SubLabels;
|
|
37
|
+
}
|
|
38
|
+
export interface Pagination {
|
|
39
|
+
total: number;
|
|
40
|
+
limit: number;
|
|
41
|
+
offset: number;
|
|
42
|
+
has_more: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* One conversion from GET /api/conversions.
|
|
46
|
+
*
|
|
47
|
+
* spend/payout are absent (key missing) when the caller's role may not see them —
|
|
48
|
+
* publisher-side roles lose `spend`, advertiser-side roles lose `payout`. `null`
|
|
49
|
+
* is different: the column is visible and simply has no amount recorded. Consumers
|
|
50
|
+
* that treat "no payout" as a signal have to tell the two apart.
|
|
51
|
+
*/
|
|
52
|
+
export interface Conversion {
|
|
53
|
+
ad_event_id: string;
|
|
54
|
+
namespace?: string;
|
|
55
|
+
click_id: string | null;
|
|
56
|
+
payload: string | null;
|
|
57
|
+
spend?: number | null;
|
|
58
|
+
payout?: number | null;
|
|
59
|
+
source_click_id?: string | null;
|
|
60
|
+
sub1?: string | null;
|
|
61
|
+
sub2?: string | null;
|
|
62
|
+
sub3?: string | null;
|
|
63
|
+
sub4?: string | null;
|
|
64
|
+
sub5?: string | null;
|
|
65
|
+
/** Unix ms when the conversion was recorded. */
|
|
66
|
+
created_at: number;
|
|
67
|
+
}
|
|
68
|
+
export interface ConversionsResponse {
|
|
69
|
+
conversions: Conversion[];
|
|
70
|
+
pagination: Pagination;
|
|
71
|
+
}
|
|
72
|
+
/** Campaign row from list/get. */
|
|
73
|
+
export interface Campaign {
|
|
74
|
+
id: number;
|
|
75
|
+
name: string;
|
|
76
|
+
status: string;
|
|
77
|
+
redirect_url?: string | null;
|
|
78
|
+
payment_model?: string;
|
|
79
|
+
rate?: number;
|
|
80
|
+
start_date?: number | null;
|
|
81
|
+
end_date?: number | null;
|
|
82
|
+
user_email?: string | null;
|
|
83
|
+
daily_budget?: number | null;
|
|
84
|
+
total_budget?: number | null;
|
|
85
|
+
pacing?: string;
|
|
86
|
+
budget_paused?: number;
|
|
87
|
+
budget_pause_reason?: string | null;
|
|
88
|
+
silent?: number;
|
|
89
|
+
payout_goal_type?: string | null;
|
|
90
|
+
created_at?: number;
|
|
91
|
+
updated_at?: number;
|
|
92
|
+
targeting_rules?: TargetingRule[];
|
|
93
|
+
}
|
|
94
|
+
/** Response of POST /api/campaigns. */
|
|
95
|
+
export interface CreateCampaignResponse {
|
|
96
|
+
id: number;
|
|
97
|
+
name: string;
|
|
98
|
+
status: string;
|
|
99
|
+
created_at: number;
|
|
100
|
+
}
|
|
101
|
+
export interface CampaignsResponse {
|
|
102
|
+
campaigns: Campaign[];
|
|
103
|
+
pagination: Pagination;
|
|
104
|
+
}
|
|
105
|
+
export interface Zone {
|
|
106
|
+
id: string;
|
|
107
|
+
name: string;
|
|
108
|
+
status: string;
|
|
109
|
+
site_url?: string | null;
|
|
110
|
+
traffic_back_url?: string | null;
|
|
111
|
+
postback_url?: string | null;
|
|
112
|
+
user_email?: string | null;
|
|
113
|
+
manager_email?: string | null;
|
|
114
|
+
/** Linked traffic source; its tracking template drives the URL tools. */
|
|
115
|
+
traffic_source_id?: string | null;
|
|
116
|
+
traffic_source_name?: string | null;
|
|
117
|
+
created_at?: number;
|
|
118
|
+
updated_at?: number;
|
|
119
|
+
}
|
|
120
|
+
export interface ZonesResponse {
|
|
121
|
+
zones: Zone[];
|
|
122
|
+
pagination: Pagination;
|
|
123
|
+
}
|
|
124
|
+
export interface CreateZoneResponse {
|
|
125
|
+
id: string;
|
|
126
|
+
status: string;
|
|
127
|
+
created_at: number;
|
|
128
|
+
}
|
|
129
|
+
export interface UpdateZoneResponse {
|
|
130
|
+
id: string;
|
|
131
|
+
updated_at: number;
|
|
132
|
+
}
|
|
133
|
+
export interface UpdateCampaignResponse {
|
|
134
|
+
id: number;
|
|
135
|
+
updated_at: number;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* One row from GET /api/traffic-sources. The stored network API credential is
|
|
139
|
+
* write-only — reads carry `has_api_token`, never the token itself.
|
|
140
|
+
*/
|
|
141
|
+
export interface TrafficSource {
|
|
142
|
+
id: string;
|
|
143
|
+
name: string;
|
|
144
|
+
preset: string | null;
|
|
145
|
+
tracking_template: string;
|
|
146
|
+
postback_template: string;
|
|
147
|
+
has_api_token: boolean;
|
|
148
|
+
status: string;
|
|
149
|
+
created_at?: number;
|
|
150
|
+
updated_at?: number;
|
|
151
|
+
/** Present on GET /api/traffic-sources/{id} only. */
|
|
152
|
+
linked_zones?: number;
|
|
153
|
+
}
|
|
154
|
+
export interface TrafficSourcesResponse {
|
|
155
|
+
traffic_sources: TrafficSource[];
|
|
156
|
+
pagination: Pagination;
|
|
157
|
+
}
|
|
158
|
+
export interface CreateTrafficSourceResponse {
|
|
159
|
+
id: string;
|
|
160
|
+
status: string;
|
|
161
|
+
created_at: number;
|
|
162
|
+
}
|
|
163
|
+
export interface UpdateTrafficSourceResponse {
|
|
164
|
+
id: string;
|
|
165
|
+
updated_at: number;
|
|
166
|
+
}
|
|
167
|
+
/** One entry from GET /api/traffic-source-presets. */
|
|
168
|
+
export interface TrafficSourcePreset {
|
|
169
|
+
id: string;
|
|
170
|
+
name: string;
|
|
171
|
+
doc_url: string;
|
|
172
|
+
tracking_template: string;
|
|
173
|
+
postback_template: string;
|
|
174
|
+
sub_meanings: Partial<Record<SubKey, string>>;
|
|
175
|
+
notes: string;
|
|
176
|
+
}
|
|
177
|
+
export interface TrafficSourcePresetsResponse {
|
|
178
|
+
presets: TrafficSourcePreset[];
|
|
179
|
+
}
|
|
180
|
+
/** Subset of GET /api/tenant this server reads. */
|
|
181
|
+
export interface TenantSettingsResponse {
|
|
182
|
+
company?: string;
|
|
183
|
+
/** IANA name; every date the tools render or resolve is in this zone. */
|
|
184
|
+
timezone?: string;
|
|
185
|
+
sub_labels?: SubLabels;
|
|
186
|
+
/** Tenant's own API domain; "" when unset. Integration URLs must use it. */
|
|
187
|
+
custom_api_domain?: string;
|
|
188
|
+
}
|
|
189
|
+
/** One payout rule from GET/POST /api/campaigns/{id}/payout_rules. */
|
|
190
|
+
export interface PayoutRule {
|
|
191
|
+
id: number;
|
|
192
|
+
campaign_id: number;
|
|
193
|
+
zone_id: string | null;
|
|
194
|
+
payout: number;
|
|
195
|
+
created_at?: number;
|
|
196
|
+
}
|
|
197
|
+
export interface PayoutRulesResponse {
|
|
198
|
+
payout_rules: PayoutRule[];
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* One team member from GET /api/api-keys?type=user, or the response of
|
|
202
|
+
* POST /api/api-keys?type=user (create).
|
|
203
|
+
*
|
|
204
|
+
* `token` is present in both. It is not a one-time secret — affset returns it
|
|
205
|
+
* on every list call to a permitted role, same as the dashboard's "Copy token"
|
|
206
|
+
* button — but `list_team` deliberately never echoes it anyway, since a
|
|
207
|
+
* browsing-style "show my team" query is a bad place for live credentials to
|
|
208
|
+
* repeatedly pass through model context. `create_team_member` is the
|
|
209
|
+
* considered exception: it echoes the token exactly once, on the one call
|
|
210
|
+
* that just minted it for the operator to hand to that person.
|
|
211
|
+
*/
|
|
212
|
+
export interface TeamMember {
|
|
213
|
+
token?: string;
|
|
214
|
+
namespace?: string;
|
|
215
|
+
user_id?: string;
|
|
216
|
+
email: string | null;
|
|
217
|
+
role: string;
|
|
218
|
+
created_at: number;
|
|
219
|
+
expires_at?: number;
|
|
220
|
+
permissions?: string[];
|
|
221
|
+
manager_email?: string;
|
|
222
|
+
}
|
|
223
|
+
/** Response of POST /api/api-keys?type=user — same shape as TeamMember, token always present. */
|
|
224
|
+
export type CreateTeamMemberResponse = TeamMember & {
|
|
225
|
+
token: string;
|
|
226
|
+
};
|
|
227
|
+
export interface TargetingRule {
|
|
228
|
+
id?: number;
|
|
229
|
+
campaign_id?: string | number;
|
|
230
|
+
targeting_rule_type_id: number;
|
|
231
|
+
targeting_method: "whitelist" | "blacklist";
|
|
232
|
+
rule: string;
|
|
233
|
+
}
|
|
234
|
+
export interface TargetingRulesResponse {
|
|
235
|
+
targeting_rules: TargetingRule[];
|
|
236
|
+
}
|
|
237
|
+
export interface TargetingRuleType {
|
|
238
|
+
id: number;
|
|
239
|
+
name: string;
|
|
240
|
+
description?: string | null;
|
|
241
|
+
}
|
|
242
|
+
export interface TargetingRuleTypesResponse {
|
|
243
|
+
targeting_rule_types: TargetingRuleType[];
|
|
244
|
+
}
|
|
245
|
+
/** The five traffic-source breakdown keys. */
|
|
246
|
+
export declare const SUB_KEYS: readonly ["sub1", "sub2", "sub3", "sub4", "sub5"];
|
|
247
|
+
export type SubKey = (typeof SUB_KEYS)[number];
|
|
248
|
+
/** Tenant display names for sub slots; only labeled slots are present. */
|
|
249
|
+
export type SubLabels = Partial<Record<SubKey, string>>;
|
|
250
|
+
/** Valid `group_by` dimensions accepted by `GET /api/stats`. */
|
|
251
|
+
export declare const GROUP_BY_VALUES: readonly ["date", "campaign_id", "zone_id", "country", "conversion_type", "publisher_email", "advertiser_email", "sub1", "sub2", "sub3", "sub4", "sub5"];
|
|
252
|
+
export type GroupBy = (typeof GROUP_BY_VALUES)[number];
|
|
253
|
+
export declare const CAMPAIGN_STATUSES: readonly ["active", "paused", "archived"];
|
|
254
|
+
export declare const ZONE_STATUSES: readonly ["active", "inactive"];
|
|
255
|
+
export declare const TRAFFIC_SOURCE_STATUSES: readonly ["active", "archived"];
|
|
256
|
+
export declare const PAYMENT_MODELS: readonly ["cpa", "cpm"];
|
|
257
|
+
export declare const PACING_VALUES: readonly ["asap", "even"];
|
package/dist/types.js
CHANGED
|
@@ -9,10 +9,12 @@ export const GROUP_BY_VALUES = [
|
|
|
9
9
|
"country",
|
|
10
10
|
"conversion_type",
|
|
11
11
|
"publisher_email",
|
|
12
|
+
"advertiser_email",
|
|
12
13
|
...SUB_KEYS,
|
|
13
14
|
];
|
|
14
15
|
export const CAMPAIGN_STATUSES = ["active", "paused", "archived"];
|
|
15
16
|
export const ZONE_STATUSES = ["active", "inactive"];
|
|
17
|
+
export const TRAFFIC_SOURCE_STATUSES = ["active", "archived"];
|
|
16
18
|
export const PAYMENT_MODELS = ["cpa", "cpm"];
|
|
17
19
|
export const PACING_VALUES = ["asap", "even"];
|
|
18
20
|
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package version, duplicated from package.json so the core stays importable
|
|
3
|
+
* on runtimes without Node's module APIs (Workers). createRequire-reading
|
|
4
|
+
* package.json here would break bundlers; a sync test guards the duplicate
|
|
5
|
+
* (see registerTools.test.ts).
|
|
6
|
+
*/
|
|
7
|
+
export declare const VERSION = "0.3.0";
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package version, duplicated from package.json so the core stays importable
|
|
3
|
+
* on runtimes without Node's module APIs (Workers). createRequire-reading
|
|
4
|
+
* package.json here would break bundlers; a sync test guards the duplicate
|
|
5
|
+
* (see registerTools.test.ts).
|
|
6
|
+
*/
|
|
7
|
+
export const VERSION = "0.3.0";
|
|
8
|
+
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@affset/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"mcpName": "io.github.affset/mcp",
|
|
5
5
|
"description": "MCP server for the affset ad platform — stats, campaigns, zones, payouts, targeting, sub labels, and team from your chat client.",
|
|
6
6
|
"type": "module",
|
|
@@ -20,6 +20,19 @@
|
|
|
20
20
|
"bin": {
|
|
21
21
|
"affset-mcp": "dist/index.js"
|
|
22
22
|
},
|
|
23
|
+
"main": "./dist/core.js",
|
|
24
|
+
"types": "./dist/core.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/core.d.ts",
|
|
28
|
+
"default": "./dist/core.js"
|
|
29
|
+
},
|
|
30
|
+
"./core": {
|
|
31
|
+
"types": "./dist/core.d.ts",
|
|
32
|
+
"default": "./dist/core.js"
|
|
33
|
+
},
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
23
36
|
"files": [
|
|
24
37
|
"dist",
|
|
25
38
|
"!dist/**/*.test.*",
|
|
@@ -28,7 +41,7 @@
|
|
|
28
41
|
".env.example"
|
|
29
42
|
],
|
|
30
43
|
"engines": {
|
|
31
|
-
"node": ">=
|
|
44
|
+
"node": ">=22.13.0"
|
|
32
45
|
},
|
|
33
46
|
"scripts": {
|
|
34
47
|
"build": "tsc",
|