@affset/mcp 0.1.0 → 0.2.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.
Files changed (66) hide show
  1. package/.env.example +5 -0
  2. package/README.md +158 -37
  3. package/dist/client.d.ts +43 -0
  4. package/dist/client.js +15 -2
  5. package/dist/config.d.ts +7 -0
  6. package/dist/config.js +15 -54
  7. package/dist/core.d.ts +18 -0
  8. package/dist/core.js +18 -0
  9. package/dist/docs.d.ts +39 -0
  10. package/dist/docs.js +105 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +0 -0
  13. package/dist/lib/format.d.ts +46 -0
  14. package/dist/lib/format.js +3 -0
  15. package/dist/lib/integrationUrls.d.ts +57 -0
  16. package/dist/lib/linkArgs.d.ts +27 -0
  17. package/dist/lib/patch.d.ts +10 -0
  18. package/dist/lib/payoutRules.d.ts +38 -0
  19. package/dist/lib/readBody.d.ts +17 -0
  20. package/dist/lib/readBody.js +69 -0
  21. package/dist/lib/targeting.d.ts +51 -0
  22. package/dist/lib/time.d.ts +38 -0
  23. package/dist/lib/toolResult.d.ts +7 -0
  24. package/dist/lib/urls.d.ts +2 -0
  25. package/dist/lib/zones.d.ts +24 -0
  26. package/dist/registerTools.d.ts +40 -0
  27. package/dist/registerTools.js +401 -0
  28. package/dist/runtimeConfig.d.ts +37 -0
  29. package/dist/runtimeConfig.js +93 -0
  30. package/dist/server.d.ts +4 -0
  31. package/dist/server.js +3 -306
  32. package/dist/tools/createCampaign.d.ts +25 -0
  33. package/dist/tools/createCampaign.js +3 -2
  34. package/dist/tools/createTeamMember.d.ts +26 -0
  35. package/dist/tools/createTeamMember.js +204 -0
  36. package/dist/tools/createZone.d.ts +22 -0
  37. package/dist/tools/cutZones.d.ts +29 -0
  38. package/dist/tools/deletePayoutRule.d.ts +16 -0
  39. package/dist/tools/getCampaign.d.ts +12 -0
  40. package/dist/tools/getCampaign.js +150 -0
  41. package/dist/tools/getStats.d.ts +40 -0
  42. package/dist/tools/getStats.js +37 -2
  43. package/dist/tools/getTrackingLink.d.ts +23 -0
  44. package/dist/tools/getZoneUrl.d.ts +21 -0
  45. package/dist/tools/listCampaigns.d.ts +23 -0
  46. package/dist/tools/listConversions.d.ts +29 -0
  47. package/dist/tools/listPayoutRules.d.ts +12 -0
  48. package/dist/tools/listSubLabels.d.ts +5 -0
  49. package/dist/tools/listTargetingRules.d.ts +12 -0
  50. package/dist/tools/listTargetingTypes.d.ts +5 -0
  51. package/dist/tools/listTeam.d.ts +14 -0
  52. package/dist/tools/listZones.d.ts +23 -0
  53. package/dist/tools/removeTargetingRule.d.ts +21 -0
  54. package/dist/tools/setCampaignStatus.d.ts +18 -0
  55. package/dist/tools/setPayoutGoal.d.ts +16 -0
  56. package/dist/tools/setPayoutRule.d.ts +18 -0
  57. package/dist/tools/setSubLabels.d.ts +22 -0
  58. package/dist/tools/setTargetingRule.d.ts +21 -0
  59. package/dist/tools/updateCampaign.d.ts +35 -0
  60. package/dist/tools/updateZone.d.ts +25 -0
  61. package/dist/tools/whoami.d.ts +6 -0
  62. package/dist/types.d.ts +210 -0
  63. package/dist/types.js +1 -0
  64. package/dist/version.d.ts +7 -0
  65. package/dist/version.js +8 -0
  66. package/package.json +16 -2
package/dist/server.js CHANGED
@@ -1,316 +1,13 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { AffsetClient } from "./client.js";
3
- import { getStats, getStatsInputSchema, GET_STATS_DESCRIPTION } from "./tools/getStats.js";
4
- import { cutZones, cutZonesInputSchema, CUT_ZONES_DESCRIPTION } from "./tools/cutZones.js";
5
- import { createCampaign, createCampaignInputSchema, CREATE_CAMPAIGN_DESCRIPTION, } from "./tools/createCampaign.js";
6
- import { listCampaigns, listCampaignsInputSchema, LIST_CAMPAIGNS_DESCRIPTION, } from "./tools/listCampaigns.js";
7
- import { listZones, listZonesInputSchema, LIST_ZONES_DESCRIPTION } from "./tools/listZones.js";
8
- import { listTeam, listTeamInputSchema, LIST_TEAM_DESCRIPTION } from "./tools/listTeam.js";
9
- import { createZone, createZoneInputSchema, CREATE_ZONE_DESCRIPTION } from "./tools/createZone.js";
10
- import { getZoneUrl, getZoneUrlInputSchema, GET_ZONE_URL_DESCRIPTION } from "./tools/getZoneUrl.js";
11
- import { getTrackingLink, getTrackingLinkInputSchema, GET_TRACKING_LINK_DESCRIPTION, } from "./tools/getTrackingLink.js";
12
- import { updateZone, updateZoneInputSchema, UPDATE_ZONE_DESCRIPTION } from "./tools/updateZone.js";
13
- import { updateCampaign, updateCampaignInputSchema, UPDATE_CAMPAIGN_DESCRIPTION, } from "./tools/updateCampaign.js";
14
- import { setCampaignStatus, setCampaignStatusInputSchema, SET_CAMPAIGN_STATUS_DESCRIPTION, } from "./tools/setCampaignStatus.js";
15
- import { listPayoutRules, listPayoutRulesInputSchema, LIST_PAYOUT_RULES_DESCRIPTION, } from "./tools/listPayoutRules.js";
16
- import { setPayoutRule, setPayoutRuleInputSchema, SET_PAYOUT_RULE_DESCRIPTION, } from "./tools/setPayoutRule.js";
17
- import { deletePayoutRule, deletePayoutRuleInputSchema, DELETE_PAYOUT_RULE_DESCRIPTION, } from "./tools/deletePayoutRule.js";
18
- import { setPayoutGoal, setPayoutGoalInputSchema, SET_PAYOUT_GOAL_DESCRIPTION, } from "./tools/setPayoutGoal.js";
19
- import { listTargetingTypes, listTargetingTypesInputSchema, LIST_TARGETING_TYPES_DESCRIPTION, } from "./tools/listTargetingTypes.js";
20
- import { listTargetingRules, listTargetingRulesInputSchema, LIST_TARGETING_RULES_DESCRIPTION, } from "./tools/listTargetingRules.js";
21
- import { setTargetingRule, setTargetingRuleInputSchema, SET_TARGETING_RULE_DESCRIPTION, } from "./tools/setTargetingRule.js";
22
- import { removeTargetingRule, removeTargetingRuleInputSchema, REMOVE_TARGETING_RULE_DESCRIPTION, } from "./tools/removeTargetingRule.js";
23
- import { listSubLabels, listSubLabelsInputSchema, LIST_SUB_LABELS_DESCRIPTION, } from "./tools/listSubLabels.js";
24
- import { setSubLabels, setSubLabelsInputSchema, SET_SUB_LABELS_DESCRIPTION, } from "./tools/setSubLabels.js";
25
- import { listConversions, listConversionsInputSchema, LIST_CONVERSIONS_DESCRIPTION, } from "./tools/listConversions.js";
26
- import { whoami, whoamiInputSchema, WHOAMI_DESCRIPTION } from "./tools/whoami.js";
27
- import { createRequire } from "node:module";
28
- const require = createRequire(import.meta.url);
29
- const { version: VERSION } = require("../package.json");
2
+ import { registerAffsetTools } from "./registerTools.js";
3
+ import { VERSION } from "./version.js";
30
4
  /** Build the MCP server with the affset tools registered against `config`. */
31
5
  export function createServer(config) {
32
- const client = new AffsetClient(config);
33
6
  const server = new McpServer({
34
7
  name: "affset-mcp",
35
8
  version: VERSION,
36
9
  });
37
- // Registers every tool as normal, then immediately removes anything that isn't
38
- // readOnlyHint:true when AFFSET_READ_ONLY is set — a mutation tool never has a
39
- // chance to appear in tools/list or be called. See AFFSET_READ_ONLY in the README
40
- // for why this exists (untrusted third-party data can reach model context via
41
- // list_conversions/get_stats, so mutation tools are an injection blast-radius
42
- // control, not just a UI nicety).
43
- const registerTool = (name, toolConfig, cb) => {
44
- const tool = server.registerTool(name, toolConfig, cb);
45
- if (config.readOnly && toolConfig.annotations?.readOnlyHint !== true) {
46
- tool.remove();
47
- }
48
- return tool;
49
- };
50
- registerTool("whoami", {
51
- title: "Show the tenant this MCP is bound to",
52
- description: WHOAMI_DESCRIPTION,
53
- inputSchema: whoamiInputSchema,
54
- annotations: {
55
- readOnlyHint: true,
56
- destructiveHint: false,
57
- idempotentHint: true,
58
- openWorldHint: true,
59
- },
60
- }, () => whoami(client, config));
61
- registerTool("get_stats", {
62
- title: "Get affset stats",
63
- description: GET_STATS_DESCRIPTION,
64
- inputSchema: getStatsInputSchema,
65
- annotations: {
66
- readOnlyHint: true,
67
- destructiveHint: false,
68
- idempotentHint: true,
69
- openWorldHint: true,
70
- },
71
- }, (args) => getStats(client, args));
72
- registerTool("list_campaigns", {
73
- title: "List campaigns",
74
- description: LIST_CAMPAIGNS_DESCRIPTION,
75
- inputSchema: listCampaignsInputSchema,
76
- annotations: {
77
- readOnlyHint: true,
78
- destructiveHint: false,
79
- idempotentHint: true,
80
- openWorldHint: true,
81
- },
82
- }, (args) => listCampaigns(client, args));
83
- registerTool("list_zones", {
84
- title: "List zones",
85
- description: LIST_ZONES_DESCRIPTION,
86
- inputSchema: listZonesInputSchema,
87
- annotations: {
88
- readOnlyHint: true,
89
- destructiveHint: false,
90
- idempotentHint: true,
91
- openWorldHint: true,
92
- },
93
- }, (args) => listZones(client, args));
94
- registerTool("list_team", {
95
- title: "List team members",
96
- description: LIST_TEAM_DESCRIPTION,
97
- inputSchema: listTeamInputSchema,
98
- annotations: {
99
- readOnlyHint: true,
100
- destructiveHint: false,
101
- idempotentHint: true,
102
- openWorldHint: true,
103
- },
104
- }, (args) => listTeam(client, args));
105
- registerTool("get_zone_url", {
106
- title: "Get the zone URL for a traffic source",
107
- description: GET_ZONE_URL_DESCRIPTION,
108
- inputSchema: getZoneUrlInputSchema,
109
- annotations: {
110
- readOnlyHint: true,
111
- destructiveHint: false,
112
- idempotentHint: true,
113
- openWorldHint: true,
114
- },
115
- }, (args) => getZoneUrl(client, config, args));
116
- registerTool("get_tracking_link", {
117
- title: "Get a campaign's tracking link",
118
- description: GET_TRACKING_LINK_DESCRIPTION,
119
- inputSchema: getTrackingLinkInputSchema,
120
- annotations: {
121
- readOnlyHint: true,
122
- destructiveHint: false,
123
- idempotentHint: true,
124
- openWorldHint: true,
125
- },
126
- }, (args) => getTrackingLink(client, config, args));
127
- registerTool("create_campaign", {
128
- title: "Create a campaign",
129
- description: CREATE_CAMPAIGN_DESCRIPTION,
130
- inputSchema: createCampaignInputSchema,
131
- annotations: {
132
- readOnlyHint: false,
133
- destructiveHint: false,
134
- idempotentHint: false,
135
- openWorldHint: true,
136
- },
137
- }, (args) => createCampaign(client, config, args));
138
- registerTool("update_campaign", {
139
- title: "Update a campaign",
140
- description: UPDATE_CAMPAIGN_DESCRIPTION,
141
- inputSchema: updateCampaignInputSchema,
142
- annotations: {
143
- readOnlyHint: false,
144
- destructiveHint: true,
145
- idempotentHint: false,
146
- openWorldHint: true,
147
- },
148
- }, (args) => updateCampaign(client, args));
149
- registerTool("set_campaign_status", {
150
- title: "Run or pause a campaign",
151
- description: SET_CAMPAIGN_STATUS_DESCRIPTION,
152
- inputSchema: setCampaignStatusInputSchema,
153
- annotations: {
154
- readOnlyHint: false,
155
- destructiveHint: true,
156
- idempotentHint: false,
157
- openWorldHint: true,
158
- },
159
- }, (args) => setCampaignStatus(client, args));
160
- registerTool("create_zone", {
161
- title: "Create a zone",
162
- description: CREATE_ZONE_DESCRIPTION,
163
- inputSchema: createZoneInputSchema,
164
- annotations: {
165
- readOnlyHint: false,
166
- destructiveHint: false,
167
- idempotentHint: false,
168
- openWorldHint: true,
169
- },
170
- }, (args) => createZone(client, args));
171
- registerTool("update_zone", {
172
- title: "Update a zone",
173
- description: UPDATE_ZONE_DESCRIPTION,
174
- inputSchema: updateZoneInputSchema,
175
- annotations: {
176
- readOnlyHint: false,
177
- destructiveHint: true,
178
- idempotentHint: false,
179
- openWorldHint: true,
180
- },
181
- }, (args) => updateZone(client, args));
182
- registerTool("cut_zones", {
183
- title: "Cut underperforming zones",
184
- description: CUT_ZONES_DESCRIPTION,
185
- inputSchema: cutZonesInputSchema,
186
- annotations: {
187
- readOnlyHint: false,
188
- destructiveHint: true,
189
- idempotentHint: false,
190
- openWorldHint: true,
191
- },
192
- }, (args) => cutZones(client, args));
193
- registerTool("list_payout_rules", {
194
- title: "List payout rules",
195
- description: LIST_PAYOUT_RULES_DESCRIPTION,
196
- inputSchema: listPayoutRulesInputSchema,
197
- annotations: {
198
- readOnlyHint: true,
199
- destructiveHint: false,
200
- idempotentHint: true,
201
- openWorldHint: true,
202
- },
203
- }, (args) => listPayoutRules(client, args));
204
- registerTool("set_payout_rule", {
205
- title: "Set a payout rule",
206
- description: SET_PAYOUT_RULE_DESCRIPTION,
207
- inputSchema: setPayoutRuleInputSchema,
208
- annotations: {
209
- readOnlyHint: false,
210
- destructiveHint: true,
211
- idempotentHint: false,
212
- openWorldHint: true,
213
- },
214
- }, (args) => setPayoutRule(client, args));
215
- registerTool("delete_payout_rule", {
216
- title: "Delete a payout rule",
217
- description: DELETE_PAYOUT_RULE_DESCRIPTION,
218
- inputSchema: deletePayoutRuleInputSchema,
219
- annotations: {
220
- readOnlyHint: false,
221
- destructiveHint: true,
222
- idempotentHint: false,
223
- openWorldHint: true,
224
- },
225
- }, (args) => deletePayoutRule(client, args));
226
- registerTool("set_payout_goal", {
227
- title: "Set payout goal type",
228
- description: SET_PAYOUT_GOAL_DESCRIPTION,
229
- inputSchema: setPayoutGoalInputSchema,
230
- annotations: {
231
- readOnlyHint: false,
232
- destructiveHint: true,
233
- idempotentHint: false,
234
- openWorldHint: true,
235
- },
236
- }, (args) => setPayoutGoal(client, args));
237
- registerTool("list_targeting_types", {
238
- title: "List targeting rule types",
239
- description: LIST_TARGETING_TYPES_DESCRIPTION,
240
- inputSchema: listTargetingTypesInputSchema,
241
- annotations: {
242
- readOnlyHint: true,
243
- destructiveHint: false,
244
- idempotentHint: true,
245
- openWorldHint: true,
246
- },
247
- }, () => listTargetingTypes(client));
248
- registerTool("list_targeting_rules", {
249
- title: "List campaign targeting rules",
250
- description: LIST_TARGETING_RULES_DESCRIPTION,
251
- inputSchema: listTargetingRulesInputSchema,
252
- annotations: {
253
- readOnlyHint: true,
254
- destructiveHint: false,
255
- idempotentHint: true,
256
- openWorldHint: true,
257
- },
258
- }, (args) => listTargetingRules(client, args));
259
- registerTool("set_targeting_rule", {
260
- title: "Set a targeting rule",
261
- description: SET_TARGETING_RULE_DESCRIPTION,
262
- inputSchema: setTargetingRuleInputSchema,
263
- annotations: {
264
- readOnlyHint: false,
265
- destructiveHint: true,
266
- idempotentHint: false,
267
- openWorldHint: true,
268
- },
269
- }, (args) => setTargetingRule(client, args));
270
- registerTool("remove_targeting_rule", {
271
- title: "Remove a targeting rule",
272
- description: REMOVE_TARGETING_RULE_DESCRIPTION,
273
- inputSchema: removeTargetingRuleInputSchema,
274
- annotations: {
275
- readOnlyHint: false,
276
- destructiveHint: true,
277
- idempotentHint: false,
278
- openWorldHint: true,
279
- },
280
- }, (args) => removeTargetingRule(client, args));
281
- registerTool("list_sub_labels", {
282
- title: "List sub labels",
283
- description: LIST_SUB_LABELS_DESCRIPTION,
284
- inputSchema: listSubLabelsInputSchema,
285
- annotations: {
286
- readOnlyHint: true,
287
- destructiveHint: false,
288
- idempotentHint: true,
289
- openWorldHint: true,
290
- },
291
- }, () => listSubLabels(client));
292
- registerTool("set_sub_labels", {
293
- title: "Set sub labels",
294
- description: SET_SUB_LABELS_DESCRIPTION,
295
- inputSchema: setSubLabelsInputSchema,
296
- annotations: {
297
- readOnlyHint: false,
298
- destructiveHint: true,
299
- idempotentHint: false,
300
- openWorldHint: true,
301
- },
302
- }, (args) => setSubLabels(client, args));
303
- registerTool("list_conversions", {
304
- title: "List conversions",
305
- description: LIST_CONVERSIONS_DESCRIPTION,
306
- inputSchema: listConversionsInputSchema,
307
- annotations: {
308
- readOnlyHint: true,
309
- destructiveHint: false,
310
- idempotentHint: true,
311
- openWorldHint: true,
312
- },
313
- }, (args) => listConversions(client, args));
10
+ registerAffsetTools(server, config);
314
11
  return server;
315
12
  }
316
13
  //# sourceMappingURL=server.js.map
@@ -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 type { Config } from "../runtimeConfig.js";
5
+ export declare const CREATE_CAMPAIGN_DESCRIPTION: string;
6
+ export declare const createCampaignInputSchema: {
7
+ user_email: z.ZodString;
8
+ offer_url: z.ZodString;
9
+ name: z.ZodOptional<z.ZodString>;
10
+ geo: z.ZodOptional<z.ZodArray<z.ZodString, "atleastone">>;
11
+ payout: z.ZodOptional<z.ZodNumber>;
12
+ zone_id: z.ZodOptional<z.ZodString>;
13
+ confirm: z.ZodDefault<z.ZodBoolean>;
14
+ };
15
+ type CreateCampaignArgs = {
16
+ user_email: string;
17
+ offer_url: string;
18
+ name?: string;
19
+ geo?: string[];
20
+ payout?: number;
21
+ zone_id?: string;
22
+ confirm: boolean;
23
+ };
24
+ export declare function createCampaign(client: AffsetClient, config: Config, args: CreateCampaignArgs): Promise<CallToolResult>;
25
+ export {};
@@ -13,7 +13,8 @@ const PAYOUT_MAX = 9999.99999;
13
13
  const NAME_MAX = 120;
14
14
  export const CREATE_CAMPAIGN_DESCRIPTION = "Create a campaign (offer) in the current namespace from a compact spec: advertiser " +
15
15
  "email, offer URL, geo whitelist, payout, name. The advertiser (user_email) is required " +
16
- "— it must already exist as a team member (same as the Advertiser dropdown in the dashboard). " +
16
+ "— it must already exist as a team member (same as the Advertiser dropdown in the dashboard); " +
17
+ "create one first with create_team_member if needed. " +
17
18
  "Everything else gets media-buying defaults: CPA model, " +
18
19
  "rate 0 (no internal advertiser billing), created paused, a global payout rule when " +
19
20
  "payout is given, and a ready-to-use tracking link prefilled with the sub convention " +
@@ -29,7 +30,7 @@ export const createCampaignInputSchema = {
29
30
  .email()
30
31
  .describe("Advertiser email that owns this campaign (required — the API rejects the call without it). " +
31
32
  "Must already exist as a team member with the advertiser role; same as the dashboard's " +
32
- "Advertiser dropdown. List candidates with list_team."),
33
+ "Advertiser dropdown. List candidates with list_team, or create one with create_team_member."),
33
34
  offer_url: z
34
35
  .string()
35
36
  .min(1)
@@ -0,0 +1,26 @@
1
+ import { z } from "zod";
2
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ import type { AffsetClient } from "../client.js";
4
+ /** Matches lite-adserver's TENANT_ROLES. */
5
+ declare const TENANT_ROLES: readonly ["owner", "manager", "advertiser_manager", "publisher_manager", "advertiser", "publisher"];
6
+ type TenantRole = (typeof TENANT_ROLES)[number];
7
+ declare const PERMISSION_VALUES: readonly ["read", "write"];
8
+ export declare const CREATE_TEAM_MEMBER_DESCRIPTION: string;
9
+ export declare const createTeamMemberInputSchema: {
10
+ email: z.ZodString;
11
+ role: z.ZodEnum<["owner", "manager", "advertiser_manager", "publisher_manager", "advertiser", "publisher"]>;
12
+ manager_email: z.ZodOptional<z.ZodString>;
13
+ permissions: z.ZodOptional<z.ZodArray<z.ZodEnum<["read", "write"]>, "atleastone">>;
14
+ expires_at: z.ZodOptional<z.ZodNumber>;
15
+ confirm: z.ZodDefault<z.ZodBoolean>;
16
+ };
17
+ type CreateTeamMemberArgs = {
18
+ email: string;
19
+ role: TenantRole;
20
+ manager_email?: string;
21
+ permissions?: (typeof PERMISSION_VALUES)[number][];
22
+ expires_at?: number;
23
+ confirm: boolean;
24
+ };
25
+ export declare function createTeamMember(client: AffsetClient, args: CreateTeamMemberArgs): Promise<CallToolResult>;
26
+ export {};
@@ -0,0 +1,204 @@
1
+ import { z } from "zod";
2
+ import { mdCell } from "../lib/format.js";
3
+ import { errorResult, textError, textResult } from "../lib/toolResult.js";
4
+ /** Matches lite-adserver's TENANT_ROLES. */
5
+ const TENANT_ROLES = [
6
+ "owner",
7
+ "manager",
8
+ "advertiser_manager",
9
+ "publisher_manager",
10
+ "advertiser",
11
+ "publisher",
12
+ ];
13
+ /** manager_email is only meaningful for these two roles. */
14
+ const MANAGED_ROLES = ["advertiser", "publisher"];
15
+ const PERMISSION_VALUES = ["read", "write"];
16
+ const MAX_EPOCH_MS = 8_640_000_000_000_000;
17
+ /** affset tokens are `sk_live_` + 64 hex (~72 chars). Bound what we echo into model context. */
18
+ const MAX_TOKEN_LENGTH = 200;
19
+ export const CREATE_TEAM_MEMBER_DESCRIPTION = "Invite a team member: create a user API key with an email in the current namespace — " +
20
+ 'the same operation as the dashboard\'s "Add Team Member". Requires owner/manager, or a ' +
21
+ "scoped manager role (publisher_manager / advertiser_manager), which can only create its " +
22
+ "own managed role (publisher / advertiser respectively) assigned to itself — the API " +
23
+ "enforces this, not this tool, so a scoped manager's role/manager_email args may be " +
24
+ "overridden. Returns the new member's plaintext API key ONCE, in the confirmed response " +
25
+ "— it is not shown again by list_team, which deliberately never echoes tokens, so copy " +
26
+ "it now and send it to them over a private channel. Does not send an invite email; the " +
27
+ "API key is the credential, handed over out of band. " +
28
+ "DRY-RUN by default; pass confirm=true to apply.";
29
+ export const createTeamMemberInputSchema = {
30
+ email: z
31
+ .string()
32
+ .trim()
33
+ .email()
34
+ .describe("New team member's email — their login / API identity."),
35
+ role: z
36
+ .enum(TENANT_ROLES)
37
+ .describe("owner | manager | advertiser_manager | publisher_manager | advertiser | publisher. " +
38
+ "What your own API key's role is allowed to create is enforced by the API."),
39
+ manager_email: z
40
+ .string()
41
+ .trim()
42
+ .email()
43
+ .optional()
44
+ .describe("Assign to a publisher_manager / advertiser_manager. Only valid with role=publisher or " +
45
+ "role=advertiser. A scoped manager key ignores this and assigns itself instead."),
46
+ permissions: z
47
+ .array(z.enum(PERMISSION_VALUES))
48
+ .nonempty()
49
+ .optional()
50
+ .describe('Defaults to ["read","write"] (matches the dashboard\'s default). "read" is always included.'),
51
+ expires_at: z
52
+ .number()
53
+ .int()
54
+ .positive()
55
+ .max(MAX_EPOCH_MS)
56
+ .optional()
57
+ .describe("Optional future expiry as epoch milliseconds. Omit for a key that never expires."),
58
+ confirm: z
59
+ .boolean()
60
+ .default(false)
61
+ .describe("false = dry-run preview (default). true = create the member and issue the key."),
62
+ };
63
+ export async function createTeamMember(client, args) {
64
+ try {
65
+ const email = args.email.trim();
66
+ if (!email)
67
+ return textError("email is required.");
68
+ const managerEmail = args.manager_email?.trim();
69
+ if (args.manager_email !== undefined && !managerEmail) {
70
+ return textError("manager_email must not be blank when provided.");
71
+ }
72
+ if (managerEmail !== undefined && !MANAGED_ROLES.includes(args.role)) {
73
+ return textError("manager_email is only allowed with role=publisher or role=advertiser.");
74
+ }
75
+ const expiryError = validateExpiry(args.expires_at);
76
+ if (expiryError)
77
+ return textError(expiryError);
78
+ const permissions = normalizePermissions(args.permissions);
79
+ const expiresNote = args.expires_at !== undefined ? fmtDay(args.expires_at) : "never";
80
+ const summaryTable = [
81
+ "| Field | Value |",
82
+ "|---|---|",
83
+ `| Email | ${mdCell(email)} |`,
84
+ `| Role | ${mdCell(args.role)} |`,
85
+ `| Manager | ${mdCell(managerEmail ?? "—")} |`,
86
+ `| Permissions | ${permissions.join(", ")} |`,
87
+ `| Expires | ${mdCell(expiresNote)} |`,
88
+ ].join("\n");
89
+ if (!args.confirm) {
90
+ return textResult([
91
+ "**Dry run** — would create a team member with:",
92
+ "",
93
+ summaryTable,
94
+ "",
95
+ "Call again with `confirm: true` to create it and issue the API key. A scoped " +
96
+ "manager key (publisher_manager / advertiser_manager) can only create its own " +
97
+ "managed role, assigned to itself — the fields above may be overridden by the API.",
98
+ ].join("\n"));
99
+ }
100
+ const body = { email, role: args.role, permissions };
101
+ if (managerEmail !== undefined)
102
+ body.manager_email = managerEmail;
103
+ if (args.expires_at !== undefined)
104
+ body.expires_at = args.expires_at;
105
+ // Treat the response as untrusted at runtime. Most importantly, do not let a
106
+ // malformed optional field throw after the non-idempotent POST succeeded: that
107
+ // would report an error and make a duplicate retry look appropriate.
108
+ const rawCreated = await client.post("/api/api-keys?type=user", body);
109
+ const created = isRecord(rawCreated) ? rawCreated : {};
110
+ const createdEmail = stringField(created.email, email);
111
+ const createdRole = stringField(created.role, args.role);
112
+ const createdManager = nullableStringField(created.manager_email, managerEmail ?? "—");
113
+ const createdPermissions = permissionFields(created.permissions, permissions);
114
+ const createdExpiry = expiryField(created.expires_at, expiresNote);
115
+ const token = extractToken(created.token);
116
+ const result = [
117
+ `✅ Team member **${mdCell(createdEmail)}** created.`,
118
+ "",
119
+ "| Field | Value |",
120
+ "|---|---|",
121
+ `| Email | ${mdCell(createdEmail)} |`,
122
+ `| Role | ${mdCell(createdRole)} |`,
123
+ `| Manager | ${mdCell(createdManager)} |`,
124
+ `| Permissions | ${createdPermissions.map(mdCell).join(", ")} |`,
125
+ `| Expires | ${mdCell(createdExpiry)} |`,
126
+ "",
127
+ ];
128
+ if (token) {
129
+ result.push("**API key** (shown once here — treat it like a password, send it over a private channel):", secretBlock(token), "`list_team` will show this person from now on, but never their token. If it leaks, " +
130
+ "revoke access from the dashboard's Team page.");
131
+ }
132
+ else {
133
+ result.push("⚠️ The member was created, but the API response did not include a usable API key. " +
134
+ "Do not retry this create call: that could create a duplicate. Retrieve or rotate " +
135
+ "the key from the dashboard's Team page.");
136
+ }
137
+ return textResult(result.join("\n"));
138
+ }
139
+ catch (err) {
140
+ return errorResult(err);
141
+ }
142
+ }
143
+ function normalizePermissions(raw) {
144
+ const set = new Set(raw && raw.length > 0 ? raw : PERMISSION_VALUES);
145
+ set.add("read");
146
+ return PERMISSION_VALUES.filter((permission) => set.has(permission));
147
+ }
148
+ function fmtDay(ms) {
149
+ return new Date(ms).toISOString().split("T", 1)[0];
150
+ }
151
+ function validateExpiry(ms) {
152
+ if (ms === undefined)
153
+ return null;
154
+ if (!Number.isInteger(ms) || ms <= 0 || ms > MAX_EPOCH_MS) {
155
+ return "expires_at must be a valid positive epoch-millisecond timestamp.";
156
+ }
157
+ if (ms <= Date.now())
158
+ return "expires_at must be in the future.";
159
+ return null;
160
+ }
161
+ function isRecord(value) {
162
+ return value !== null && typeof value === "object" && !Array.isArray(value);
163
+ }
164
+ function extractToken(value) {
165
+ if (typeof value !== "string")
166
+ return null;
167
+ const token = value.trim();
168
+ if (!token || token.length > MAX_TOKEN_LENGTH)
169
+ return null;
170
+ return token;
171
+ }
172
+ function stringField(value, fallback) {
173
+ return typeof value === "string" && value.trim() ? value : fallback;
174
+ }
175
+ function nullableStringField(value, fallback) {
176
+ if (value === null)
177
+ return "—";
178
+ return stringField(value, fallback);
179
+ }
180
+ function permissionFields(value, fallback) {
181
+ if (!Array.isArray(value))
182
+ return fallback;
183
+ const permissions = value.filter((permission) => typeof permission === "string" && permission.trim() !== "");
184
+ return permissions.length > 0 ? permissions : fallback;
185
+ }
186
+ function expiryField(value, fallback) {
187
+ if (value === null)
188
+ return "never";
189
+ if (typeof value !== "number" || !Number.isFinite(value))
190
+ return fallback;
191
+ try {
192
+ return fmtDay(value);
193
+ }
194
+ catch {
195
+ return fallback;
196
+ }
197
+ }
198
+ /** Use a fence longer than any backtick run in the secret, so it cannot close the block. */
199
+ function secretBlock(secret) {
200
+ const longestRun = Math.max(0, ...(secret.match(/`+/g) ?? []).map((run) => run.length));
201
+ const fence = "`".repeat(Math.max(3, longestRun + 1));
202
+ return `${fence}\n${secret}\n${fence}`;
203
+ }
204
+ //# sourceMappingURL=createTeamMember.js.map
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ import type { AffsetClient } from "../client.js";
4
+ export declare const CREATE_ZONE_DESCRIPTION: string;
5
+ export declare const createZoneInputSchema: {
6
+ name: z.ZodString;
7
+ postback_url: z.ZodOptional<z.ZodString>;
8
+ site_url: z.ZodOptional<z.ZodString>;
9
+ traffic_back_url: z.ZodOptional<z.ZodString>;
10
+ user_email: z.ZodOptional<z.ZodString>;
11
+ confirm: z.ZodDefault<z.ZodBoolean>;
12
+ };
13
+ type CreateZoneArgs = {
14
+ name: string;
15
+ postback_url?: string;
16
+ site_url?: string;
17
+ traffic_back_url?: string;
18
+ user_email?: string;
19
+ confirm: boolean;
20
+ };
21
+ export declare function createZone(client: AffsetClient, args: CreateZoneArgs): Promise<CallToolResult>;
22
+ export {};
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ import type { AffsetClient } from "../client.js";
4
+ import { RANGE_PRESETS } from "../lib/time.js";
5
+ export declare const CUT_ZONES_DESCRIPTION: string;
6
+ export declare const cutZonesInputSchema: {
7
+ campaign_id: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
8
+ cr_max: z.ZodOptional<z.ZodNumber>;
9
+ spend_min: z.ZodOptional<z.ZodNumber>;
10
+ roi_max: z.ZodOptional<z.ZodNumber>;
11
+ min_clicks: z.ZodDefault<z.ZodNumber>;
12
+ range: z.ZodOptional<z.ZodEnum<["today", "yesterday", "last_7_days", "last_30_days", "this_month"]>>;
13
+ from: z.ZodOptional<z.ZodString>;
14
+ to: z.ZodOptional<z.ZodString>;
15
+ confirm: z.ZodDefault<z.ZodBoolean>;
16
+ };
17
+ type CutZonesArgs = {
18
+ campaign_id: string | number;
19
+ cr_max?: number;
20
+ spend_min?: number;
21
+ roi_max?: number;
22
+ min_clicks: number;
23
+ range?: (typeof RANGE_PRESETS)[number];
24
+ from?: string;
25
+ to?: string;
26
+ confirm: boolean;
27
+ };
28
+ export declare function cutZones(client: AffsetClient, args: CutZonesArgs): Promise<CallToolResult>;
29
+ export {};
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ import { type AffsetClient } from "../client.js";
4
+ export declare const DELETE_PAYOUT_RULE_DESCRIPTION: string;
5
+ export declare const deletePayoutRuleInputSchema: {
6
+ campaign_id: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
7
+ zone_id: z.ZodOptional<z.ZodString>;
8
+ confirm: z.ZodDefault<z.ZodBoolean>;
9
+ };
10
+ type DeletePayoutRuleArgs = {
11
+ campaign_id: string | number;
12
+ zone_id?: string;
13
+ confirm: boolean;
14
+ };
15
+ export declare function deletePayoutRule(client: AffsetClient, args: DeletePayoutRuleArgs): Promise<CallToolResult>;
16
+ export {};
@@ -0,0 +1,12 @@
1
+ import { z } from "zod";
2
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ import { type AffsetClient } from "../client.js";
4
+ export declare const GET_CAMPAIGN_DESCRIPTION: string;
5
+ export declare const getCampaignInputSchema: {
6
+ campaign_id: z.ZodUnion<[z.ZodEffects<z.ZodString, string, string>, z.ZodNumber]>;
7
+ };
8
+ type GetCampaignArgs = {
9
+ campaign_id: string | number;
10
+ };
11
+ export declare function getCampaign(client: AffsetClient, args: GetCampaignArgs): Promise<CallToolResult>;
12
+ export {};