@jclvsh/iris 0.2.3 → 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/README.md CHANGED
@@ -1,32 +1,108 @@
1
1
  # @jclvsh/iris
2
2
 
3
- MCP server for the [Iris](https://iris-ai.dev) API — AI-powered messaging assistant for automated multi-channel outreach campaigns.
3
+ MCP server and CLI for the [Iris](https://iris-ai.dev) API — AI-powered messaging assistant for automated multi-channel outreach campaigns. Lets AI agents (Claude Code, Cursor) and humans manage campaigns, contacts, conversations, AI generations, discovery, channels, and webhooks.
4
4
 
5
- ## Install
5
+ ## Installation
6
6
 
7
- The package ships two binaries: `iris-mcp` (the MCP server, used in MCP host configs below) and `iris` (the CLI, for terminal use). One-shot run:
7
+ ```bash
8
+ npm install -g @jclvsh/iris
9
+ ```
10
+
11
+ This installs two binaries:
12
+
13
+ - `iris` — CLI for terminal use
14
+ - `iris-mcp` — MCP server for AI agents
15
+
16
+ Or run one-shot without installing:
8
17
 
9
18
  ```bash
10
19
  npx -y -p @jclvsh/iris iris-mcp
11
20
  ```
12
21
 
13
- ## Configuration
22
+ ## CLI
23
+
24
+ ```bash
25
+ # set your API key
26
+ export IRIS_API_KEY=is_live_...
27
+
28
+ # manage campaigns
29
+ iris campaigns list
30
+ iris campaigns create -n "spring outreach" -o product_promotion --product-name "acme widgets"
31
+ iris campaigns analyze <id>
32
+ iris campaigns usage <id>
33
+
34
+ # manage contacts
35
+ iris contacts list --campaign-id <id>
36
+ iris contacts create --campaign-id <id> -e jane@example.com --full-name "Jane Doe" --company-name "Acme"
37
+
38
+ # review the AI approval queue
39
+ iris messages pending --campaign-id <id>
40
+ iris messages approve <id>
41
+ iris messages reject <id> --reason "off-topic"
42
+ iris messages bulk -a approve --ids id1,id2,id3
43
+
44
+ # manage webhooks
45
+ iris webhooks create -u https://example.com/hook -e campaign.created,message.delivered
46
+ iris webhooks test <id>
47
+ iris webhooks deliveries <id>
48
+
49
+ # check your key
50
+ iris keys me
51
+
52
+ # table output
53
+ iris --format table campaigns list
54
+
55
+ # full help
56
+ iris --help
57
+ iris campaigns --help
58
+ ```
59
+
60
+ ### CLI Commands
61
+
62
+ | Group | Alias | Commands | Count |
63
+ | --- | --- | --- | --- |
64
+ | campaigns | `c` | list, get, create, update, delete, usage, analyze | 7 |
65
+ | contacts | `ct` | list, get, create, update, delete, usage | 6 |
66
+ | messages | `m` | pending, approve, reject, retry, feedback, bulk | 6 |
67
+ | conversations | `conv` | list, get, messages, read | 4 |
68
+ | discovery | `d` | search, search-campaign, sources, job | 4 |
69
+ | ai | — | generate, generate-campaign, analyze, usage, suggestions | 5 |
70
+ | keys | `k` | me, list, create, rename, revoke | 5 |
71
+ | webhooks | `w` | list, create, get, update, delete, test, deliveries | 7 |
72
+ | notifications | `n` | list, update, delete, read-all | 4 |
73
+ | analytics | `a` | overview, campaign, performance, response-time, contact-types | 5 |
74
+ | billing | `b` | plans | 1 |
75
+ | channels | `ch` | accounts, availability, send | 3 |
76
+ | credits | `cr` | balance, buy | 2 |
77
+
78
+ ## MCP Server Configuration
79
+
80
+ ### Environment Variables
81
+
82
+ | Variable | Required | Description |
83
+ | --- | --- | --- |
84
+ | `IRIS_API_KEY` | Yes | API key (must start with `is_live_`) |
85
+ | `IRIS_API_URL` | No | API base URL (default: `https://iris-ai.dev/api/v1`) |
86
+
87
+ > **Note:** the args use `-p @jclvsh/iris iris-mcp` to explicitly select the MCP-server bin. The package ships two binaries (`iris` for the CLI, `iris-mcp` for the MCP server), so a bare `npx @jclvsh/iris` would launch the CLI by default.
88
+
89
+ Get your API key at [iris-ai.dev/settings/api-keys](https://iris-ai.dev/settings/api-keys).
14
90
 
15
91
  ### Claude Code
16
92
 
17
- Add to your Claude Code settings:
93
+ Add to your `.claude/settings.json`:
18
94
 
19
95
  ```json
20
96
  {
21
- "mcpServers": {
22
- "iris": {
23
- "command": "npx",
24
- "args": ["-y", "-p", "@jclvsh/iris", "iris-mcp"],
25
- "env": {
26
- "IRIS_API_KEY": "is_live_..."
27
- }
28
- }
29
- }
97
+ "mcpServers": {
98
+ "iris": {
99
+ "command": "npx",
100
+ "args": ["-y", "-p", "@jclvsh/iris", "iris-mcp"],
101
+ "env": {
102
+ "IRIS_API_KEY": "is_live_your_key_here"
103
+ }
104
+ }
105
+ }
30
106
  }
31
107
  ```
32
108
 
@@ -36,45 +112,306 @@ Add to `.cursor/mcp.json`:
36
112
 
37
113
  ```json
38
114
  {
39
- "mcpServers": {
40
- "iris": {
41
- "command": "npx",
42
- "args": ["-y", "-p", "@jclvsh/iris", "iris-mcp"],
43
- "env": {
44
- "IRIS_API_KEY": "is_live_..."
45
- }
46
- }
47
- }
115
+ "mcpServers": {
116
+ "iris": {
117
+ "command": "npx",
118
+ "args": ["-y", "-p", "@jclvsh/iris", "iris-mcp"],
119
+ "env": {
120
+ "IRIS_API_KEY": "is_live_your_key_here"
121
+ }
122
+ }
123
+ }
48
124
  }
49
125
  ```
50
126
 
51
- ## Environment Variables
127
+ ## Available Tools
52
128
 
53
- | Variable | Required | Description |
54
- |----------|----------|-------------|
55
- | `IRIS_API_KEY` | Yes | API key (must start with `is_live_`) |
56
- | `IRIS_API_URL` | No | Base URL (default: `https://iris-ai.dev/api/v1`) |
129
+ ### Campaigns
57
130
 
58
- Get your API key at [iris-ai.dev/settings/api-keys](https://iris-ai.dev/settings/api-keys).
131
+ | Tool | Description |
132
+ | --- | --- |
133
+ | `list_campaigns` | List campaigns with optional status filter |
134
+ | `create_campaign` | Create a new outreach campaign |
135
+ | `get_campaign` | Get a campaign by ID with stats |
136
+ | `update_campaign` | Update a campaign's name, status, or follow-up settings |
137
+ | `delete_campaign` | Soft-delete a campaign (sets status to `deleted`) |
138
+ | `list_campaign_contacts` | List contacts in a campaign with optional status filter |
139
+ | `get_campaign_usage` | Get usage limits and current counts for a campaign |
140
+ | `analyze_campaign` | Trigger AI analysis to generate target profiles |
141
+ | `list_target_profiles` | List target profiles for a campaign |
142
+ | `create_target_profile` | Create a target profile for a campaign |
143
+ | `delete_target_profile` | Delete a target profile from a campaign |
59
144
 
60
- ## Available Tools
145
+ #### `create_campaign` Parameters
146
+
147
+ | Parameter | Type | Required | Description |
148
+ | --- | --- | --- | --- |
149
+ | `name` | string (1-100) | Yes | Campaign name |
150
+ | `objective_type` | enum | Yes | `venue_dj`, `venue_host`, `product_promotion`, `custom` |
151
+ | `product_name` | string (≤200) | No | Product name (for `product_promotion`) |
152
+ | `product_url` | string (URL) | No | Product URL (for `product_promotion`) |
153
+ | `product_description` | string (≤2000) | No | Product description (for `product_promotion`) |
154
+
155
+ #### `update_campaign` Parameters
156
+
157
+ | Parameter | Type | Required | Description |
158
+ | --- | --- | --- | --- |
159
+ | `id` | string (UUID) | Yes | Campaign ID |
160
+ | `name` | string (1-100) | No | New campaign name |
161
+ | `status` | enum | No | `draft`, `active`, `paused`, `completed`, `deleted` |
162
+ | `follow_up_enabled` | boolean | No | Enable follow-up emails |
163
+ | `follow_up_max_count` | number (0-10) | No | Max follow-up count |
164
+ | `follow_up_delay_days` | number (1-30) | No | Days between follow-ups |
165
+
166
+ #### `create_target_profile` Parameters
167
+
168
+ | Parameter | Type | Required | Description |
169
+ | --- | --- | --- | --- |
170
+ | `id` | string (UUID) | Yes | Campaign ID |
171
+ | `name` | string (1-200) | Yes | Profile name |
172
+ | `profile_type` | enum | Yes | `local_business`, `content_creator`, `influencer`, `b2b_professional`, `ecommerce`, `saas_company`, `agency`, `freelancer`, `other` |
173
+ | `job_titles` | string[] | No | Target job titles |
174
+ | `industries` | string[] | No | Target industries |
175
+ | `discovery_keywords` | string[] | No | Keywords for contact discovery |
176
+ | `recommended_channels` | string[] | No | `email`, `instagram`, `twitter`, `linkedin` |
177
+
178
+ ### Contacts
179
+
180
+ | Tool | Description |
181
+ | --- | --- |
182
+ | `list_contacts` | List contacts with search |
183
+ | `create_contact` | Create a new contact |
184
+ | `get_contact` | Get a contact by ID |
185
+ | `update_contact` | Update a contact's details |
186
+ | `delete_contact` | Soft-delete a contact |
187
+ | `bulk_import_contacts` | Import up to 500 contacts at once |
188
+ | `get_contact_usage` | Get usage limits for the billing period |
189
+ | `list_contact_conversations` | List conversations for a specific contact |
190
+
191
+ #### `create_contact` Parameters
192
+
193
+ | Parameter | Type | Required | Description |
194
+ | --- | --- | --- | --- |
195
+ | `email` | string (email) | Yes | Contact email address |
196
+ | `first_name` | string (≤100) | No | First name |
197
+ | `last_name` | string (≤100) | No | Last name |
198
+ | `company_name` | string (≤200) | No | Company name |
199
+ | `job_title` | string (≤200) | No | Job title |
200
+ | `phone` | string (≤50) | No | Phone number |
201
+ | `website` | string (URL) | No | Website URL |
202
+ | `tags` | string[] | No | Tags for categorization |
203
+
204
+ #### `bulk_import_contacts` Parameters
205
+
206
+ | Parameter | Type | Required | Description |
207
+ | --- | --- | --- | --- |
208
+ | `contacts` | object[] (1-500) | Yes | Array of contacts (`email`, `first_name?`, `last_name?`, `company_name?`, `job_title?`, `tags?`) |
209
+ | `campaign_id` | string (UUID) | No | Campaign to add contacts to |
210
+
211
+ ### Messages
212
+
213
+ | Tool | Description |
214
+ | --- | --- |
215
+ | `list_pending_messages` | List messages pending approval |
216
+ | `approve_message` | Approve a single pending message |
217
+ | `reject_message` | Reject a single pending message |
218
+ | `retry_message` | Retry a failed message send |
219
+ | `submit_message_feedback` | Submit feedback to improve future AI generations |
220
+ | `bulk_message_action` | Bulk approve or reject pending messages |
61
221
 
62
- | Category | Tools |
63
- |----------|-------|
64
- | Campaigns | list, create, get, update, delete, contacts, usage, analyze, target profiles |
65
- | Contacts | list, create, get, update, delete, bulk import, usage, conversations |
66
- | Conversations | list, get, messages, mark read, engagement metrics |
67
- | Messages | list pending, approve, reject, retry, feedback, bulk action |
68
- | AI | generate content, campaign content, analyze response, usage, suggestions |
69
- | Discovery | search contacts, campaign search, job status, sources |
70
- | Analytics | overview, campaign analytics, performance, response time, contact types |
71
- | Billing | plans |
72
- | Channels | connected accounts, availability, send message |
73
- | Notifications | list, update, delete, mark all read |
74
- | API Keys | current key, list, create, revoke |
75
- | Webhooks | list, create, get, update, delete, test, deliveries |
76
- | Agent Credits | balance, purchase pack (x402/MPP only) |
222
+ ### Conversations
223
+
224
+ | Tool | Description |
225
+ | --- | --- |
226
+ | `list_conversations` | List conversations with optional channel/status filters |
227
+ | `get_conversation` | Get a conversation by ID |
228
+ | `get_conversation_messages` | List messages in a conversation |
229
+ | `mark_conversation_read` | Mark a conversation as read |
230
+ | `get_engagement_metrics` | Get engagement metrics across conversations |
231
+
232
+ ### AI
233
+
234
+ | Tool | Description |
235
+ | --- | --- |
236
+ | `generate_content` | Generate AI content for a contact conversation (initial email, follow-up, reply) |
237
+ | `generate_campaign_content` | Generate campaign-level content (email templates, subject lines, briefs) |
238
+ | `analyze_response` | Analyze inbound message sentiment, intent, and suggested action |
239
+ | `get_ai_usage` | Get AI generation usage limits |
240
+ | `get_ai_suggestions` | Get AI-powered suggestions for a conversation |
241
+
242
+ #### `generate_content` Parameters
243
+
244
+ | Parameter | Type | Required | Description |
245
+ | --- | --- | --- | --- |
246
+ | `contact_id` | string (UUID) | Yes | Contact ID |
247
+ | `conversation_id` | string (UUID) | Yes | Conversation ID |
248
+ | `generation_type` | enum | Yes | `initial_email`, `follow_up`, `reply` |
249
+ | `context` | string (≤2000) | No | Additional context for the AI |
250
+
251
+ ### Discovery
252
+
253
+ | Tool | Description |
254
+ | --- | --- |
255
+ | `search_contacts` | Search for contacts by criteria (location, keywords, sources) |
256
+ | `search_campaign_contacts` | Run discovery scoped to a campaign's target profiles |
257
+ | `get_discovery_job` | Get the status of a discovery job |
258
+ | `list_discovery_sources` | List available discovery sources (Google Maps, social) |
259
+
260
+ ### Channels
261
+
262
+ | Tool | Description |
263
+ | --- | --- |
264
+ | `list_connected_accounts` | List your connected social accounts (Twitter, Instagram, LinkedIn) |
265
+ | `check_channel_availability` | Check which channels are reachable for a contact |
266
+ | `send_channel_message` | Send a message via a specific channel |
267
+
268
+ ### Analytics
269
+
270
+ | Tool | Description |
271
+ | --- | --- |
272
+ | `get_analytics_overview` | Account-level analytics overview |
273
+ | `get_campaign_analytics` | Per-campaign performance metrics |
274
+ | `get_performance_metrics` | Aggregate performance across campaigns |
275
+ | `get_response_time` | Response time distribution |
276
+ | `get_contact_types` | Engagement broken down by contact type |
277
+
278
+ ### Notifications
279
+
280
+ | Tool | Description |
281
+ | --- | --- |
282
+ | `list_notifications` | List your notifications |
283
+ | `update_notification` | Mark a notification as read or update its state |
284
+ | `delete_notification` | Delete a notification |
285
+ | `mark_all_notifications_read` | Mark every unread notification as read |
286
+
287
+ ### Billing
288
+
289
+ | Tool | Description |
290
+ | --- | --- |
291
+ | `get_billing_plans` | List available subscription plans |
292
+
293
+ ### API Keys
294
+
295
+ | Tool | Description |
296
+ | --- | --- |
297
+ | `get_current_api_key` | Get details about the currently authenticated key |
298
+ | `list_api_keys` | List all API keys for the account |
299
+ | `create_api_key` | Create a new API key (raw key returned once) |
300
+ | `revoke_api_key` | Revoke (delete) an API key |
301
+
302
+ #### `create_api_key` Parameters
303
+
304
+ | Parameter | Type | Required | Description |
305
+ | --- | --- | --- | --- |
306
+ | `name` | string (1-100) | Yes | Key name for identification |
307
+ | `scopes` | string[] | No | Permission scopes (defaults to all scopes) — see [Scopes](#api-key--scopes) |
308
+ | `expires_at` | string (ISO 8601) | No | Expiration date |
309
+
310
+ ### Webhooks
311
+
312
+ | Tool | Description |
313
+ | --- | --- |
314
+ | `list_webhooks` | List all configured webhooks |
315
+ | `create_webhook` | Create a new webhook endpoint |
316
+ | `get_webhook` | Get a webhook by ID |
317
+ | `update_webhook` | Update a webhook's URL, events, or active status |
318
+ | `delete_webhook` | Delete a webhook |
319
+ | `test_webhook` | Send a test event to a webhook URL |
320
+ | `rotate_webhook_secret` | Rotate the signing secret (new secret returned once) |
321
+ | `list_webhook_deliveries` | List delivery history for a webhook |
322
+
323
+ #### `create_webhook` Parameters
324
+
325
+ | Parameter | Type | Required | Description |
326
+ | --- | --- | --- | --- |
327
+ | `url` | string (HTTPS URL) | Yes | Webhook endpoint URL — HTTPS only |
328
+ | `events` | string[] (≥1) | Yes | Events to subscribe to — see [Webhook Events](#webhook-events) |
329
+ | `description` | string (≤500) | No | Webhook description |
330
+
331
+ #### `update_webhook` Parameters
332
+
333
+ | Parameter | Type | Required | Description |
334
+ | --- | --- | --- | --- |
335
+ | `id` | string (UUID) | Yes | Webhook ID |
336
+ | `url` | string (HTTPS URL) | No | New endpoint URL |
337
+ | `events` | string[] (≥1) | No | New events to subscribe to |
338
+ | `active` | boolean | No | Enable or disable the webhook |
339
+
340
+ ### Agent Credits
341
+
342
+ > Credits apply only to agents authenticating via x402 (USDC on Base) or MPP (Stripe). API key users do not need credits.
343
+
344
+ | Tool | Description |
345
+ | --- | --- |
346
+ | `get_credit_balance` | Check the agent credit balance |
347
+ | `purchase_credit_pack` | Purchase a 5-credit pack via MPP ($12.00) |
348
+
349
+ ## API Key & Scopes
350
+
351
+ Generate an API key at [iris-ai.dev/settings/api-keys](https://iris-ai.dev/settings/api-keys).
352
+
353
+ API keys support fine-grained scopes per resource:
354
+
355
+ | Scope | Access |
356
+ | --- | --- |
357
+ | `campaigns:read` / `campaigns:write` | Read or modify campaigns |
358
+ | `contacts:read` / `contacts:write` | Read or modify contacts |
359
+ | `conversations:read` / `conversations:write` | Read conversations or send messages |
360
+ | `messages:read` / `messages:write` | Read the queue, approve/reject/send |
361
+ | `ai:read` / `ai:write` | Read AI usage or trigger generations |
362
+ | `discovery:read` / `discovery:write` | Read sources or run discovery jobs |
363
+ | `analytics:read` | Read analytics dashboards |
364
+ | `billing:read` | Read billing plans |
365
+ | `channels:read` / `channels:write` | Read connected accounts or send via channels |
366
+ | `notifications:read` / `notifications:write` | Read or modify notifications |
367
+ | `webhooks:read` / `webhooks:write` | Read or manage webhook subscriptions |
368
+ | `keys:read` / `keys:write` | Read or manage API keys |
369
+
370
+ ### Recommended Scopes by Use Case
371
+
372
+ | Use Case | Recommended Scopes |
373
+ | --- | --- |
374
+ | AI agents / MCP | `campaigns:read`, `campaigns:write`, `contacts:read`, `contacts:write`, `messages:read`, `messages:write`, `ai:read`, `ai:write`, `conversations:read`, `conversations:write` |
375
+ | Read-only monitoring | `*:read` scopes only |
376
+ | Webhook management | `webhooks:read`, `webhooks:write` |
377
+ | Discovery automation | `discovery:read`, `discovery:write`, `contacts:write`, `campaigns:read` |
378
+
379
+ ## Webhook Events
380
+
381
+ | Event | Description |
382
+ | --- | --- |
383
+ | `campaign.created` | New campaign created |
384
+ | `campaign.updated` | Campaign details changed |
385
+ | `campaign.deleted` | Campaign soft-deleted |
386
+ | `contact.created` | New contact added |
387
+ | `contact.updated` | Contact details changed |
388
+ | `contact.deleted` | Contact soft-deleted |
389
+ | `conversation.created` | New conversation started |
390
+ | `conversation.reply_received` | Inbound reply received |
391
+ | `message.sent` | Outbound message sent |
392
+ | `message.delivered` | Message confirmed delivered |
393
+ | `message.opened` | Email opened (filtered for bots) |
394
+ | `message.bounced` | Message bounced |
395
+ | `message.approved` | Message approved in the queue |
396
+ | `message.rejected` | Message rejected in the queue |
397
+ | `discovery.completed` | Discovery job finished successfully |
398
+ | `discovery.failed` | Discovery job failed |
399
+
400
+ Webhook deliveries are signed with HMAC-SHA256 using the secret returned from `create_webhook` / `rotate_webhook_secret`. Verify the signature in the `X-Iris-Signature` header before trusting the payload.
77
401
 
78
402
  ## Documentation
79
403
 
80
- Full API documentation: [iris-ai.dev/docs](https://iris-ai.dev/docs)
404
+ Full API reference: [iris-ai.dev/docs](https://iris-ai.dev/docs)
405
+
406
+ ## Development
407
+
408
+ ```bash
409
+ # install dependencies
410
+ npm install
411
+
412
+ # build
413
+ npm run build
414
+
415
+ # run the MCP server
416
+ IRIS_API_KEY=is_live_... npm start
417
+ ```
package/dist/cli.js CHANGED
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
2
3
  import { Command } from "commander";
3
4
  import { IrisClient } from "./client.js";
4
5
  import { handleError } from "./cli-utils.js";
6
+ const pkg = createRequire(import.meta.url)("../package.json");
5
7
  import { registerCampaignCommands } from "./commands/campaigns.js";
6
8
  import { registerContactCommands } from "./commands/contacts.js";
7
9
  import { registerMessageCommands } from "./commands/messages.js";
@@ -18,7 +20,7 @@ import { registerCreditCommands } from "./commands/credits.js";
18
20
  const program = new Command()
19
21
  .name("iris")
20
22
  .description("iris CLI - manage campaigns, contacts, messages, and outreach")
21
- .version("0.2.0")
23
+ .version(pkg.version)
22
24
  .option("--json", "output as JSON (default)")
23
25
  .option("--format <fmt>", "output format: json, table", "json");
24
26
  try {
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 = {};
@@ -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 {
@@ -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 }) => {
@@ -1,5 +1,5 @@
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
4
  server.tool("list_connected_accounts", "list all connected social media accounts. requires channels:read scope.", {}, async () => {
5
5
  try {
@@ -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 discovery:write 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);
@@ -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 discovery:read scope.", {
37
78
  id: z.string().uuid().describe("discovery job ID"),
38
79
  }, async ({ id }) => {
39
80
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jclvsh/iris",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "MCP server for the Iris API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",