@jclvsh/iris 0.2.4 → 0.4.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/dist/client.js CHANGED
@@ -1,3 +1,32 @@
1
+ function safeStringify(value) {
2
+ if (Array.isArray(value)) {
3
+ const parts = value
4
+ .map((entry) => {
5
+ if (typeof entry === "string")
6
+ return entry;
7
+ if (entry && typeof entry === "object") {
8
+ const msg = entry.msg;
9
+ if (typeof msg === "string")
10
+ return msg;
11
+ }
12
+ try {
13
+ return JSON.stringify(entry);
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ })
19
+ .filter((p) => typeof p === "string" && p.length > 0);
20
+ if (parts.length > 0)
21
+ return parts.join("; ");
22
+ }
23
+ try {
24
+ return JSON.stringify(value);
25
+ }
26
+ catch {
27
+ return String(value);
28
+ }
29
+ }
1
30
  export class IrisClient {
2
31
  apiKey;
3
32
  baseUrl;
@@ -77,7 +106,13 @@ export class IrisClient {
77
106
  try {
78
107
  const body = await response.json();
79
108
  if (body?.error) {
80
- const parts = [body.error.message || "unknown error"];
109
+ const rawMessage = body.error.message;
110
+ const messageStr = typeof rawMessage === "string" && rawMessage.trim() !== ""
111
+ ? rawMessage
112
+ : rawMessage != null
113
+ ? safeStringify(rawMessage)
114
+ : "unknown error";
115
+ const parts = [messageStr];
81
116
  if (body.error.code)
82
117
  parts.unshift(`[${body.error.code}]`);
83
118
  if (body.error.field)
@@ -28,7 +28,7 @@ export function registerChannelCommands(program, client) {
28
28
  channels
29
29
  .command("send")
30
30
  .description("send a message through a specific channel in a conversation")
31
- .requiredOption("--channel <channel>", "channel: email, instagram, twitter, linkedin")
31
+ .requiredOption("--channel <channel>", "channel: email, instagram, twitter")
32
32
  .requiredOption("--conversation-id <id>", "conversation ID")
33
33
  .requiredOption("--recipient-id <id>", "recipient (email for email channel; platform handle/ID for social)")
34
34
  .requiredOption("-c, --content <text>", "message content")
@@ -11,7 +11,7 @@ export function registerConversationCommands(program, client) {
11
11
  .option("-s, --page-size <n>", "items per page (max 100)", "50")
12
12
  .option("--status <status>", "filter by status")
13
13
  .option("--campaign-id <id>", "filter by campaign")
14
- .option("--channel <channel>", "filter by channel: email, instagram, twitter, linkedin")
14
+ .option("--channel <channel>", "filter by channel: email, instagram, twitter")
15
15
  .action(async (opts) => {
16
16
  try {
17
17
  const params = {};
@@ -29,7 +29,7 @@ export function registerKeyCommands(program, client) {
29
29
  .command("create")
30
30
  .description("create a new API key (requires admin scope)")
31
31
  .requiredOption("-n, --name <name>", "key name")
32
- .option("--scopes <list>", "comma-separated scopes: campaigns:read, campaigns:write, contacts:read, contacts:write, messages:read, messages:write, conversations:read, conversations:write, discovery:write, ai:write, notifications:read, notifications:write, webhooks:read, webhooks:write", parseList)
32
+ .option("--scopes <list>", "comma-separated scopes: read, write, delete, generate, send, admin (defaults to all)", parseList)
33
33
  .action(async (opts) => {
34
34
  try {
35
35
  const body = { name: opts.name };
@@ -7,7 +7,7 @@ export function registerMessageCommands(program, client) {
7
7
  .option("-p, --page <n>", "page number", "1")
8
8
  .option("-s, --page-size <n>", "items per page (max 100)", "50")
9
9
  .option("--campaign-id <id>", "filter by campaign")
10
- .option("--channel <channel>", "filter by channel: email, instagram, twitter, linkedin")
10
+ .option("--channel <channel>", "filter by channel: email, instagram, twitter")
11
11
  .option("--status <status>", "queue status (default: pending_approval)")
12
12
  .action(async (opts) => {
13
13
  try {
package/dist/tools/ai.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  export function registerAiTools(server, client) {
3
- server.tool("generate_content", "generate AI content for a specific contact conversation (email draft, follow-up, reply). requires ai:write scope.", {
3
+ server.tool("generate_content", "generate AI content for a specific contact conversation (email draft, follow-up, reply). requires generate scope.", {
4
4
  contact_id: z.string().uuid().describe("contact ID"),
5
5
  conversation_id: z.string().uuid().describe("conversation ID"),
6
6
  generation_type: z
@@ -19,7 +19,7 @@ export function registerAiTools(server, client) {
19
19
  };
20
20
  }
21
21
  });
22
- server.tool("generate_campaign_content", "generate AI content at the campaign level (email templates, subject lines). requires ai:write scope.", {
22
+ server.tool("generate_campaign_content", "generate AI content at the campaign level (email templates, subject lines). requires generate scope.", {
23
23
  campaign_id: z.string().uuid().describe("campaign ID"),
24
24
  content_type: z
25
25
  .enum(["email_template", "subject_lines", "campaign_brief"])
@@ -36,7 +36,7 @@ export function registerAiTools(server, client) {
36
36
  };
37
37
  }
38
38
  });
39
- server.tool("analyze_response", "analyze an inbound message using AI to determine sentiment, intent, and suggested actions. requires ai:write scope.", {
39
+ server.tool("analyze_response", "analyze an inbound message using AI to determine sentiment, intent, and suggested actions. requires generate scope.", {
40
40
  conversation_id: z.string().uuid().describe("conversation ID"),
41
41
  message_content: z.string().min(1).max(10000).describe("message content to analyze"),
42
42
  }, async (args) => {
@@ -51,7 +51,7 @@ export function registerAiTools(server, client) {
51
51
  };
52
52
  }
53
53
  });
54
- server.tool("get_ai_usage", "get AI generation usage limits and current counts for the billing period. requires ai:read scope.", {}, async () => {
54
+ server.tool("get_ai_usage", "get AI generation usage limits and current counts for the billing period. requires read scope.", {}, async () => {
55
55
  try {
56
56
  const result = await client.get("/ai/usage");
57
57
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -63,7 +63,7 @@ export function registerAiTools(server, client) {
63
63
  };
64
64
  }
65
65
  });
66
- server.tool("get_ai_suggestions", "get AI-powered suggestions for a conversation (next steps, reply ideas). requires ai:read scope.", {
66
+ server.tool("get_ai_suggestions", "get AI-powered suggestions for a conversation (next steps, reply ideas). requires read scope.", {
67
67
  conversation_id: z.string().uuid().describe("conversation ID"),
68
68
  }, async ({ conversation_id }) => {
69
69
  try {
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  export function registerAnalyticsTools(server, client) {
3
- server.tool("get_analytics_overview", "get a high-level overview of all campaign analytics. requires analytics:read scope.", {}, async () => {
3
+ server.tool("get_analytics_overview", "get a high-level overview of all campaign analytics. requires read scope.", {}, async () => {
4
4
  try {
5
5
  const result = await client.get("/analytics/overview");
6
6
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -12,7 +12,7 @@ export function registerAnalyticsTools(server, client) {
12
12
  };
13
13
  }
14
14
  });
15
- server.tool("get_campaign_analytics", "get detailed analytics for a specific campaign. requires analytics:read scope.", {
15
+ server.tool("get_campaign_analytics", "get detailed analytics for a specific campaign. requires read scope.", {
16
16
  id: z.string().uuid().describe("campaign ID"),
17
17
  }, async ({ id }) => {
18
18
  try {
@@ -26,7 +26,7 @@ export function registerAnalyticsTools(server, client) {
26
26
  };
27
27
  }
28
28
  });
29
- server.tool("get_performance_metrics", "get email performance metrics over time. requires analytics:read scope.", {
29
+ server.tool("get_performance_metrics", "get email performance metrics over time. requires read scope.", {
30
30
  days: z
31
31
  .number()
32
32
  .int()
@@ -52,7 +52,7 @@ export function registerAnalyticsTools(server, client) {
52
52
  };
53
53
  }
54
54
  });
55
- server.tool("get_response_time", "get average response time analytics. requires analytics:read scope.", {
55
+ server.tool("get_response_time", "get average response time analytics. requires read scope.", {
56
56
  campaign_id: z.string().uuid().optional().describe("filter by campaign ID"),
57
57
  }, async ({ campaign_id }) => {
58
58
  try {
@@ -69,7 +69,7 @@ export function registerAnalyticsTools(server, client) {
69
69
  };
70
70
  }
71
71
  });
72
- server.tool("get_contact_types", "get breakdown of contacts by type/source. requires analytics:read scope.", {}, async () => {
72
+ server.tool("get_contact_types", "get breakdown of contacts by type/source. requires read scope.", {}, async () => {
73
73
  try {
74
74
  const result = await client.get("/analytics/contact-types");
75
75
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -1,5 +1,5 @@
1
1
  export function registerBillingTools(server, client) {
2
- server.tool("get_billing_plans", "get all available billing plans with pricing and feature limits. requires billing:read scope.", {}, async () => {
2
+ server.tool("get_billing_plans", "get all available billing plans with pricing and feature limits. requires read scope.", {}, async () => {
3
3
  try {
4
4
  const result = await client.get("/billing/plans");
5
5
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -13,7 +13,7 @@ const TARGET_PROFILE_TYPES = [
13
13
  "other",
14
14
  ];
15
15
  export function registerCampaignTools(server, client) {
16
- server.tool("list_campaigns", "list all campaigns with optional filtering by status. requires campaigns:read scope.", {
16
+ server.tool("list_campaigns", "list all campaigns with optional filtering by status. requires read scope.", {
17
17
  page: z.number().int().positive().optional().describe("page number (default 1)"),
18
18
  page_size: z.number().int().min(1).max(100).optional().describe("items per page (default 50, max 100)"),
19
19
  status: z.enum(CAMPAIGN_STATUSES).optional().describe("filter by campaign status"),
@@ -36,7 +36,7 @@ export function registerCampaignTools(server, client) {
36
36
  };
37
37
  }
38
38
  });
39
- server.tool("create_campaign", "create a new outreach campaign. requires campaigns:write scope.", {
39
+ server.tool("create_campaign", "create a new outreach campaign. requires write scope.", {
40
40
  name: z.string().min(1).max(100).describe("campaign name"),
41
41
  objective_type: z.enum(OBJECTIVE_TYPES).describe("campaign objective type"),
42
42
  product_name: z.string().max(200).optional().describe("product name (for product_promotion objective)"),
@@ -54,7 +54,7 @@ export function registerCampaignTools(server, client) {
54
54
  };
55
55
  }
56
56
  });
57
- server.tool("get_campaign", "get a campaign by ID with stats. requires campaigns:read scope.", {
57
+ server.tool("get_campaign", "get a campaign by ID with stats. requires read scope.", {
58
58
  id: z.string().uuid().describe("campaign ID"),
59
59
  }, async ({ id }) => {
60
60
  try {
@@ -68,7 +68,7 @@ export function registerCampaignTools(server, client) {
68
68
  };
69
69
  }
70
70
  });
71
- server.tool("update_campaign", "update a campaign's details. requires campaigns:write scope.", {
71
+ server.tool("update_campaign", "update a campaign's details. requires write scope.", {
72
72
  id: z.string().uuid().describe("campaign ID"),
73
73
  name: z.string().min(1).max(100).optional().describe("new campaign name"),
74
74
  status: z.enum(CAMPAIGN_STATUSES).optional().describe("new campaign status"),
@@ -87,7 +87,7 @@ export function registerCampaignTools(server, client) {
87
87
  };
88
88
  }
89
89
  });
90
- server.tool("delete_campaign", "soft-delete a campaign (sets status to deleted). requires campaigns:write scope.", {
90
+ server.tool("delete_campaign", "soft-delete a campaign (sets status to deleted). requires write scope.", {
91
91
  id: z.string().uuid().describe("campaign ID"),
92
92
  }, async ({ id }) => {
93
93
  try {
@@ -101,7 +101,7 @@ export function registerCampaignTools(server, client) {
101
101
  };
102
102
  }
103
103
  });
104
- server.tool("list_campaign_contacts", "list contacts in a campaign with optional status filter. requires campaigns:read scope.", {
104
+ server.tool("list_campaign_contacts", "list contacts in a campaign with optional status filter. requires read scope.", {
105
105
  id: z.string().uuid().describe("campaign ID"),
106
106
  page: z.number().int().positive().optional().describe("page number (default 1)"),
107
107
  page_size: z.number().int().min(1).max(100).optional().describe("items per page (default 50, max 100)"),
@@ -128,7 +128,7 @@ export function registerCampaignTools(server, client) {
128
128
  };
129
129
  }
130
130
  });
131
- server.tool("get_campaign_usage", "get usage limits and current counts for a campaign. requires campaigns:read scope.", {
131
+ server.tool("get_campaign_usage", "get usage limits and current counts for a campaign. requires read scope.", {
132
132
  id: z.string().uuid().describe("campaign ID"),
133
133
  }, async ({ id }) => {
134
134
  try {
@@ -142,7 +142,7 @@ export function registerCampaignTools(server, client) {
142
142
  };
143
143
  }
144
144
  });
145
- server.tool("analyze_campaign", "trigger AI analysis of a campaign to generate target profiles and recommendations. requires campaigns:write scope.", {
145
+ server.tool("analyze_campaign", "trigger AI analysis of a campaign to generate target profiles and recommendations. requires write scope.", {
146
146
  id: z.string().uuid().describe("campaign ID"),
147
147
  }, async ({ id }) => {
148
148
  try {
@@ -156,7 +156,7 @@ export function registerCampaignTools(server, client) {
156
156
  };
157
157
  }
158
158
  });
159
- server.tool("list_target_profiles", "list target profiles for a campaign. requires campaigns:read scope.", {
159
+ server.tool("list_target_profiles", "list target profiles for a campaign. requires read scope.", {
160
160
  id: z.string().uuid().describe("campaign ID"),
161
161
  }, async ({ id }) => {
162
162
  try {
@@ -170,7 +170,7 @@ export function registerCampaignTools(server, client) {
170
170
  };
171
171
  }
172
172
  });
173
- server.tool("create_target_profile", "create a target profile for a campaign. requires campaigns:write scope.", {
173
+ server.tool("create_target_profile", "create a target profile for a campaign. requires write scope.", {
174
174
  id: z.string().uuid().describe("campaign ID"),
175
175
  name: z.string().min(1).max(200).describe("profile name"),
176
176
  profile_type: z.enum(TARGET_PROFILE_TYPES).describe("type of target profile"),
@@ -178,7 +178,7 @@ export function registerCampaignTools(server, client) {
178
178
  industries: z.array(z.string()).optional().describe("target industries"),
179
179
  discovery_keywords: z.array(z.string()).optional().describe("keywords for contact discovery"),
180
180
  recommended_channels: z
181
- .array(z.enum(["email", "instagram", "twitter", "linkedin"]))
181
+ .array(z.enum(["email", "instagram", "twitter"]))
182
182
  .optional()
183
183
  .describe("recommended outreach channels"),
184
184
  }, async ({ id, ...body }) => {
@@ -193,7 +193,7 @@ export function registerCampaignTools(server, client) {
193
193
  };
194
194
  }
195
195
  });
196
- server.tool("delete_target_profile", "delete a target profile from a campaign. requires campaigns:write scope.", {
196
+ server.tool("delete_target_profile", "delete a target profile from a campaign. requires write scope.", {
197
197
  id: z.string().uuid().describe("campaign ID"),
198
198
  profile_id: z.string().uuid().describe("target profile ID"),
199
199
  }, async ({ id, profile_id }) => {
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
- const CHANNELS = ["email", "instagram", "twitter", "linkedin"];
2
+ const CHANNELS = ["email", "instagram", "twitter"];
3
3
  export function registerChannelTools(server, client) {
4
- server.tool("list_connected_accounts", "list all connected social media accounts. requires channels:read scope.", {}, async () => {
4
+ server.tool("list_connected_accounts", "list all connected social media accounts. requires read scope.", {}, async () => {
5
5
  try {
6
6
  const result = await client.get("/channels/accounts");
7
7
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -13,7 +13,7 @@ export function registerChannelTools(server, client) {
13
13
  };
14
14
  }
15
15
  });
16
- server.tool("check_channel_availability", "check which outreach channels are available and configured. requires channels:read scope.", {}, async () => {
16
+ server.tool("check_channel_availability", "check which outreach channels are available and configured. requires read scope.", {}, async () => {
17
17
  try {
18
18
  const result = await client.get("/channels/availability");
19
19
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -25,7 +25,7 @@ export function registerChannelTools(server, client) {
25
25
  };
26
26
  }
27
27
  });
28
- server.tool("send_channel_message", "send a message through a specific channel in a conversation. requires channels:write scope.", {
28
+ server.tool("send_channel_message", "send a message through a specific channel in a conversation. requires send scope.", {
29
29
  channel: z.enum(CHANNELS).describe("outreach channel to use"),
30
30
  conversation_id: z.string().uuid().describe("conversation ID"),
31
31
  recipient_id: z
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  export function registerContactTools(server, client) {
3
- server.tool("list_contacts", "list all contacts with optional search. requires contacts:read scope.", {
3
+ server.tool("list_contacts", "list all contacts with optional search. requires read scope.", {
4
4
  page: z.number().int().positive().optional().describe("page number (default 1)"),
5
5
  page_size: z.number().int().min(1).max(100).optional().describe("items per page (default 50, max 100)"),
6
6
  search: z.string().optional().describe("search by name, email, or company"),
@@ -23,7 +23,7 @@ export function registerContactTools(server, client) {
23
23
  };
24
24
  }
25
25
  });
26
- server.tool("create_contact", "create a new contact. requires contacts:write scope.", {
26
+ server.tool("create_contact", "create a new contact. requires write scope.", {
27
27
  email: z.string().email().describe("contact email address"),
28
28
  first_name: z.string().max(100).optional().describe("first name"),
29
29
  last_name: z.string().max(100).optional().describe("last name"),
@@ -44,7 +44,7 @@ export function registerContactTools(server, client) {
44
44
  };
45
45
  }
46
46
  });
47
- server.tool("get_contact", "get a contact by ID. requires contacts:read scope.", {
47
+ server.tool("get_contact", "get a contact by ID. requires read scope.", {
48
48
  id: z.string().uuid().describe("contact ID"),
49
49
  }, async ({ id }) => {
50
50
  try {
@@ -58,7 +58,7 @@ export function registerContactTools(server, client) {
58
58
  };
59
59
  }
60
60
  });
61
- server.tool("update_contact", "update a contact's details. requires contacts:write scope.", {
61
+ server.tool("update_contact", "update a contact's details. requires write scope.", {
62
62
  id: z.string().uuid().describe("contact ID"),
63
63
  email: z.string().email().optional().describe("email address"),
64
64
  first_name: z.string().max(100).optional().describe("first name"),
@@ -80,7 +80,7 @@ export function registerContactTools(server, client) {
80
80
  };
81
81
  }
82
82
  });
83
- server.tool("delete_contact", "soft-delete a contact. requires contacts:write scope.", {
83
+ server.tool("delete_contact", "soft-delete a contact. requires write scope.", {
84
84
  id: z.string().uuid().describe("contact ID"),
85
85
  }, async ({ id }) => {
86
86
  try {
@@ -94,7 +94,7 @@ export function registerContactTools(server, client) {
94
94
  };
95
95
  }
96
96
  });
97
- server.tool("bulk_import_contacts", "import multiple contacts at once, optionally adding them to a campaign. requires contacts:write scope.", {
97
+ server.tool("bulk_import_contacts", "import multiple contacts at once, optionally adding them to a campaign. requires write scope.", {
98
98
  contacts: z
99
99
  .array(z.object({
100
100
  email: z.string().email().describe("contact email"),
@@ -120,7 +120,7 @@ export function registerContactTools(server, client) {
120
120
  };
121
121
  }
122
122
  });
123
- server.tool("get_contact_usage", "get contact usage limits and current counts for the billing period. requires contacts:read scope.", {}, async () => {
123
+ server.tool("get_contact_usage", "get contact usage limits and current counts for the billing period. requires read scope.", {}, async () => {
124
124
  try {
125
125
  const result = await client.get("/contacts/usage");
126
126
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -132,7 +132,7 @@ export function registerContactTools(server, client) {
132
132
  };
133
133
  }
134
134
  });
135
- server.tool("list_contact_conversations", "list all conversations for a specific contact. requires contacts:read scope.", {
135
+ server.tool("list_contact_conversations", "list all conversations for a specific contact. requires read scope.", {
136
136
  id: z.string().uuid().describe("contact ID"),
137
137
  }, async ({ id }) => {
138
138
  try {
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  const CONVERSATION_STATUSES = ["active", "completed", "failed", "human_review"];
3
3
  export function registerConversationTools(server, client) {
4
- server.tool("list_conversations", "list conversations with optional filtering. requires conversations:read scope.", {
4
+ server.tool("list_conversations", "list conversations with optional filtering. requires read scope.", {
5
5
  page: z.number().int().positive().optional().describe("page number (default 1)"),
6
6
  page_size: z.number().int().min(1).max(100).optional().describe("items per page (default 50, max 100)"),
7
7
  status: z.enum(CONVERSATION_STATUSES).optional().describe("filter by conversation status"),
@@ -27,7 +27,7 @@ export function registerConversationTools(server, client) {
27
27
  };
28
28
  }
29
29
  });
30
- server.tool("get_conversation", "get a conversation by ID with contact and campaign details. requires conversations:read scope.", {
30
+ server.tool("get_conversation", "get a conversation by ID with contact and campaign details. requires read scope.", {
31
31
  id: z.string().uuid().describe("conversation ID"),
32
32
  }, async ({ id }) => {
33
33
  try {
@@ -41,7 +41,7 @@ export function registerConversationTools(server, client) {
41
41
  };
42
42
  }
43
43
  });
44
- server.tool("get_conversation_messages", "get all messages in a conversation. requires conversations:read scope.", {
44
+ server.tool("get_conversation_messages", "get all messages in a conversation. requires read scope.", {
45
45
  id: z.string().uuid().describe("conversation ID"),
46
46
  }, async ({ id }) => {
47
47
  try {
@@ -55,7 +55,7 @@ export function registerConversationTools(server, client) {
55
55
  };
56
56
  }
57
57
  });
58
- server.tool("mark_conversation_read", "mark a conversation as read. requires conversations:write scope.", {
58
+ server.tool("mark_conversation_read", "mark a conversation as read. requires write scope.", {
59
59
  id: z.string().uuid().describe("conversation ID"),
60
60
  }, async ({ id }) => {
61
61
  try {
@@ -69,7 +69,7 @@ export function registerConversationTools(server, client) {
69
69
  };
70
70
  }
71
71
  });
72
- server.tool("get_engagement_metrics", "get overall engagement metrics across all conversations. requires conversations:read scope.", {}, async () => {
72
+ server.tool("get_engagement_metrics", "get overall engagement metrics across all conversations. requires read scope.", {}, async () => {
73
73
  try {
74
74
  const result = await client.get("/conversations/engagement");
75
75
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -1,11 +1,52 @@
1
1
  import { z } from "zod";
2
- const DISCOVERY_SOURCES = ["google_maps", "instagram", "twitter", "linkedin"];
2
+ const DISCOVERY_SOURCES = ["google_maps", "instagram", "twitter", "tiktok", "reddit"];
3
+ const PROFILE_TYPES = [
4
+ "local_business",
5
+ "content_creator",
6
+ "influencer",
7
+ "b2b_professional",
8
+ "ecommerce",
9
+ "saas_company",
10
+ "agency",
11
+ "freelancer",
12
+ "other",
13
+ ];
3
14
  export function registerDiscoveryTools(server, client) {
4
- server.tool("search_contacts", "search for new contacts using external data sources (Google Maps, social platforms). requires discovery:write scope.", {
5
- query: z.string().min(1).max(500).describe("search query"),
6
- source: z.enum(DISCOVERY_SOURCES).describe("data source to search"),
7
- max_results: z.number().int().min(1).max(100).optional().describe("maximum number of results (default 20)"),
8
- campaign_id: z.string().uuid().optional().describe("campaign to add discovered contacts to"),
15
+ server.tool("search_contacts", "kick off a multi-source contact discovery job (Apify-backed). returns a job_id and contacts when ready (waits up to 90s; if status=queued, poll get_discovery_job). closed source list: google_maps, instagram, twitter, tiktok, reddit. requires generate scope.", {
16
+ keywords: z
17
+ .array(z.string().min(1))
18
+ .min(1)
19
+ .describe("non-empty array of search keywords (e.g. ['boutique hotel', 'rooftop venue'])"),
20
+ location: z
21
+ .string()
22
+ .optional()
23
+ .describe("location for geo-scoped goals (local_business / venue / brick-and-mortar). omit for global goals (creators, b2b, saas)."),
24
+ radius_miles: z.number().positive().optional().default(25).describe("search radius in miles (default 25)"),
25
+ max_results: z
26
+ .number()
27
+ .int()
28
+ .min(10)
29
+ .max(500)
30
+ .optional()
31
+ .describe("max contacts to return (10-500, default 50; server clamps to plan cap)"),
32
+ sources: z
33
+ .array(z.enum(DISCOVERY_SOURCES))
34
+ .optional()
35
+ .describe("override the default per-profile_type source mix. valid: google_maps, instagram, twitter, tiktok, reddit. omit to let the orchestrator pick."),
36
+ profile_type: z
37
+ .enum(PROFILE_TYPES)
38
+ .optional()
39
+ .describe("ICP profile type — drives default source mix when sources is omitted"),
40
+ campaign_id: z
41
+ .string()
42
+ .uuid()
43
+ .optional()
44
+ .describe("campaign to attach discovered contacts to on finalize"),
45
+ enrich_emails: z
46
+ .boolean()
47
+ .optional()
48
+ .default(true)
49
+ .describe("run waterfall email enrichment (BetterContact → FullEnrich → ZeroBounce). default true."),
9
50
  }, async (args) => {
10
51
  try {
11
52
  const result = await client.post("/discovery/search", args);
@@ -18,7 +59,7 @@ export function registerDiscoveryTools(server, client) {
18
59
  };
19
60
  }
20
61
  });
21
- server.tool("search_campaign_contacts", "search for contacts matching a campaign's target profiles. requires discovery:write scope.", {
62
+ server.tool("search_campaign_contacts", "search for contacts matching a campaign's target profiles. requires generate scope.", {
22
63
  campaign_id: z.string().uuid().describe("campaign ID"),
23
64
  target_profile_id: z.string().uuid().optional().describe("specific target profile to search for"),
24
65
  }, async (args) => {
@@ -33,7 +74,7 @@ export function registerDiscoveryTools(server, client) {
33
74
  };
34
75
  }
35
76
  });
36
- server.tool("get_discovery_job", "get the status and results of a discovery job. requires discovery:read scope.", {
77
+ server.tool("get_discovery_job", "get the status, progress, and contacts of a discovery job. status values: pending, running, completed, failed. contacts populated only when status=completed. requires read scope.", {
37
78
  id: z.string().uuid().describe("discovery job ID"),
38
79
  }, async ({ id }) => {
39
80
  try {
@@ -47,7 +88,7 @@ export function registerDiscoveryTools(server, client) {
47
88
  };
48
89
  }
49
90
  });
50
- server.tool("list_discovery_sources", "list available discovery data sources and their status. requires discovery:read scope.", {}, async () => {
91
+ server.tool("list_discovery_sources", "list available discovery data sources and their status. requires read scope.", {}, async () => {
51
92
  try {
52
93
  const result = await client.get("/discovery/sources");
53
94
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -1,30 +1,7 @@
1
1
  import { z } from "zod";
2
- const SCOPES = [
3
- "campaigns:read",
4
- "campaigns:write",
5
- "contacts:read",
6
- "contacts:write",
7
- "conversations:read",
8
- "conversations:write",
9
- "messages:read",
10
- "messages:write",
11
- "ai:read",
12
- "ai:write",
13
- "discovery:read",
14
- "discovery:write",
15
- "analytics:read",
16
- "billing:read",
17
- "channels:read",
18
- "channels:write",
19
- "notifications:read",
20
- "notifications:write",
21
- "webhooks:read",
22
- "webhooks:write",
23
- "keys:read",
24
- "keys:write",
25
- ];
2
+ const SCOPES = ["read", "write", "delete", "generate", "send", "admin"];
26
3
  export function registerKeyTools(server, client) {
27
- server.tool("get_current_api_key", "get details about the currently authenticated API key. requires keys:read scope.", {}, async () => {
4
+ server.tool("get_current_api_key", "get details about the currently authenticated API key.", {}, async () => {
28
5
  try {
29
6
  const result = await client.get("/keys/me");
30
7
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -36,7 +13,7 @@ export function registerKeyTools(server, client) {
36
13
  };
37
14
  }
38
15
  });
39
- server.tool("list_api_keys", "list all API keys for the account. requires keys:read scope.", {
16
+ server.tool("list_api_keys", "list all API keys for the account. requires admin scope.", {
40
17
  page: z.number().int().positive().optional().describe("page number (default 1)"),
41
18
  page_size: z.number().int().min(1).max(100).optional().describe("items per page (default 50, max 100)"),
42
19
  }, async ({ page, page_size }) => {
@@ -56,12 +33,12 @@ export function registerKeyTools(server, client) {
56
33
  };
57
34
  }
58
35
  });
59
- server.tool("create_api_key", "create a new API key. requires keys:write scope.", {
36
+ server.tool("create_api_key", "create a new API key. requires admin scope.", {
60
37
  name: z.string().min(1).max(100).describe("key name for identification"),
61
38
  scopes: z
62
39
  .array(z.enum(SCOPES))
63
40
  .optional()
64
- .describe('permission scopes (e.g. ["campaigns:read", "contacts:write"]). defaults to all scopes'),
41
+ .describe('permission scopes (any of: read, write, delete, generate, send, admin). defaults to all scopes'),
65
42
  expires_at: z.string().optional().describe("expiration date in ISO 8601 format"),
66
43
  }, async (args) => {
67
44
  try {
@@ -75,7 +52,7 @@ export function registerKeyTools(server, client) {
75
52
  };
76
53
  }
77
54
  });
78
- server.tool("revoke_api_key", "revoke (delete) an API key. requires keys:write scope.", {
55
+ server.tool("revoke_api_key", "revoke (delete) an API key. requires admin scope.", {
79
56
  id: z.string().uuid().describe("API key ID to revoke"),
80
57
  }, async ({ id }) => {
81
58
  try {
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  export function registerMessageTools(server, client) {
3
- server.tool("list_pending_messages", "list messages awaiting approval before sending. requires messages:read scope.", {
3
+ server.tool("list_pending_messages", "list messages awaiting approval before sending. requires read scope.", {
4
4
  page: z.number().int().positive().optional().describe("page number (default 1)"),
5
5
  page_size: z.number().int().min(1).max(100).optional().describe("items per page (default 50, max 100)"),
6
6
  campaign_id: z.string().uuid().optional().describe("filter by campaign ID"),
@@ -23,7 +23,7 @@ export function registerMessageTools(server, client) {
23
23
  };
24
24
  }
25
25
  });
26
- server.tool("approve_message", "approve a pending message for sending. requires messages:write scope.", {
26
+ server.tool("approve_message", "approve a pending message for sending. requires send scope.", {
27
27
  id: z.string().uuid().describe("message ID"),
28
28
  }, async ({ id }) => {
29
29
  try {
@@ -37,7 +37,7 @@ export function registerMessageTools(server, client) {
37
37
  };
38
38
  }
39
39
  });
40
- server.tool("reject_message", "reject a pending message with an optional reason. requires messages:write scope.", {
40
+ server.tool("reject_message", "reject a pending message with an optional reason. requires send scope.", {
41
41
  id: z.string().uuid().describe("message ID"),
42
42
  reason: z.string().max(500).optional().describe("reason for rejection"),
43
43
  }, async ({ id, reason }) => {
@@ -52,7 +52,7 @@ export function registerMessageTools(server, client) {
52
52
  };
53
53
  }
54
54
  });
55
- server.tool("retry_message", "retry sending a failed message. requires messages:write scope.", {
55
+ server.tool("retry_message", "retry sending a failed message. requires send scope.", {
56
56
  id: z.string().uuid().describe("message ID"),
57
57
  }, async ({ id }) => {
58
58
  try {
@@ -66,7 +66,7 @@ export function registerMessageTools(server, client) {
66
66
  };
67
67
  }
68
68
  });
69
- server.tool("submit_message_feedback", "submit feedback on an AI-generated message to improve future generations. requires messages:write scope.", {
69
+ server.tool("submit_message_feedback", "submit feedback on an AI-generated message to improve future generations. requires send scope.", {
70
70
  id: z.string().uuid().describe("message ID"),
71
71
  feedback: z.string().min(1).max(2000).describe("feedback text"),
72
72
  }, async ({ id, feedback }) => {
@@ -81,7 +81,7 @@ export function registerMessageTools(server, client) {
81
81
  };
82
82
  }
83
83
  });
84
- server.tool("bulk_message_action", "approve or reject multiple messages at once. requires messages:write scope.", {
84
+ server.tool("bulk_message_action", "approve or reject multiple messages at once. requires send scope.", {
85
85
  action: z.enum(["approve", "reject"]).describe("action to perform"),
86
86
  ids: z.array(z.string().uuid()).min(1).max(100).describe("message IDs to act on"),
87
87
  }, async (args) => {
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  export function registerNotificationTools(server, client) {
3
- server.tool("list_notifications", "list user notifications. requires notifications:read scope.", {
3
+ server.tool("list_notifications", "list user notifications. requires read scope.", {
4
4
  page: z.number().int().positive().optional().describe("page number (default 1)"),
5
5
  page_size: z.number().int().min(1).max(100).optional().describe("items per page (default 50, max 100)"),
6
6
  }, async ({ page, page_size }) => {
@@ -20,7 +20,7 @@ export function registerNotificationTools(server, client) {
20
20
  };
21
21
  }
22
22
  });
23
- server.tool("update_notification", "update a notification (e.g., mark as read). requires notifications:write scope.", {
23
+ server.tool("update_notification", "update a notification (e.g., mark as read). requires write scope.", {
24
24
  id: z.string().uuid().describe("notification ID"),
25
25
  read: z.boolean().optional().describe("mark as read or unread"),
26
26
  }, async ({ id, ...body }) => {
@@ -35,7 +35,7 @@ export function registerNotificationTools(server, client) {
35
35
  };
36
36
  }
37
37
  });
38
- server.tool("delete_notification", "delete a notification. requires notifications:write scope.", {
38
+ server.tool("delete_notification", "delete a notification. requires write scope.", {
39
39
  id: z.string().uuid().describe("notification ID"),
40
40
  }, async ({ id }) => {
41
41
  try {
@@ -49,7 +49,7 @@ export function registerNotificationTools(server, client) {
49
49
  };
50
50
  }
51
51
  });
52
- server.tool("mark_all_notifications_read", "mark all notifications as read. requires notifications:write scope.", {}, async () => {
52
+ server.tool("mark_all_notifications_read", "mark all notifications as read. requires write scope.", {}, async () => {
53
53
  try {
54
54
  await client.post("/notifications/read-all");
55
55
  return { content: [{ type: "text", text: "all notifications marked as read" }] };
@@ -20,7 +20,7 @@ const WEBHOOK_EVENTS = [
20
20
  "discovery.failed",
21
21
  ];
22
22
  export function registerWebhookTools(server, client) {
23
- server.tool("list_webhooks", "list all configured webhooks. requires webhooks:read scope.", {
23
+ server.tool("list_webhooks", "list all configured webhooks. requires admin scope.", {
24
24
  page: z.number().int().positive().optional().describe("page number (default 1)"),
25
25
  page_size: z.number().int().min(1).max(100).optional().describe("items per page (default 50, max 100)"),
26
26
  }, async ({ page, page_size }) => {
@@ -40,7 +40,7 @@ export function registerWebhookTools(server, client) {
40
40
  };
41
41
  }
42
42
  });
43
- server.tool("create_webhook", "create a new webhook endpoint. requires webhooks:write scope.", {
43
+ server.tool("create_webhook", "create a new webhook endpoint. requires admin scope.", {
44
44
  url: z
45
45
  .string()
46
46
  .url()
@@ -60,7 +60,7 @@ export function registerWebhookTools(server, client) {
60
60
  };
61
61
  }
62
62
  });
63
- server.tool("get_webhook", "get a webhook by ID. requires webhooks:read scope.", {
63
+ server.tool("get_webhook", "get a webhook by ID. requires admin scope.", {
64
64
  id: z.string().uuid().describe("webhook ID"),
65
65
  }, async ({ id }) => {
66
66
  try {
@@ -74,7 +74,7 @@ export function registerWebhookTools(server, client) {
74
74
  };
75
75
  }
76
76
  });
77
- server.tool("update_webhook", "update a webhook's configuration. requires webhooks:write scope.", {
77
+ server.tool("update_webhook", "update a webhook's configuration. requires admin scope.", {
78
78
  id: z.string().uuid().describe("webhook ID"),
79
79
  url: z
80
80
  .string()
@@ -96,7 +96,7 @@ export function registerWebhookTools(server, client) {
96
96
  };
97
97
  }
98
98
  });
99
- server.tool("delete_webhook", "delete a webhook. requires webhooks:write scope.", {
99
+ server.tool("delete_webhook", "delete a webhook. requires admin scope.", {
100
100
  id: z.string().uuid().describe("webhook ID"),
101
101
  }, async ({ id }) => {
102
102
  try {
@@ -110,7 +110,7 @@ export function registerWebhookTools(server, client) {
110
110
  };
111
111
  }
112
112
  });
113
- server.tool("test_webhook", "send a test event to a webhook endpoint to verify it's working. requires webhooks:write scope.", {
113
+ server.tool("test_webhook", "send a test event to a webhook endpoint to verify it's working. requires admin scope.", {
114
114
  id: z.string().uuid().describe("webhook ID"),
115
115
  }, async ({ id }) => {
116
116
  try {
@@ -124,7 +124,7 @@ export function registerWebhookTools(server, client) {
124
124
  };
125
125
  }
126
126
  });
127
- server.tool("rotate_webhook_secret", "rotate the signing secret for a webhook. the new secret is returned in the response. requires webhooks:write scope.", {
127
+ server.tool("rotate_webhook_secret", "rotate the signing secret for a webhook. the new secret is returned in the response. requires admin scope.", {
128
128
  id: z.string().uuid().describe("webhook ID"),
129
129
  }, async ({ id }) => {
130
130
  try {
@@ -138,7 +138,7 @@ export function registerWebhookTools(server, client) {
138
138
  };
139
139
  }
140
140
  });
141
- server.tool("list_webhook_deliveries", "list delivery history for a webhook. requires webhooks:read scope.", {
141
+ server.tool("list_webhook_deliveries", "list delivery history for a webhook. requires admin scope.", {
142
142
  id: z.string().uuid().describe("webhook ID"),
143
143
  page: z.number().int().positive().optional().describe("page number (default 1)"),
144
144
  page_size: z.number().int().min(1).max(100).optional().describe("items per page (default 50, max 100)"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jclvsh/iris",
3
- "version": "0.2.4",
3
+ "version": "0.4.0",
4
4
  "description": "MCP server for the Iris API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",