@jclvsh/iris 0.23.1 → 0.24.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 +22 -0
- package/dist/cli.js +2 -0
- package/dist/commands/affiliate.d.ts +3 -0
- package/dist/commands/affiliate.js +15 -0
- package/dist/hosted-transport.d.ts +19 -0
- package/dist/hosted-transport.js +67 -0
- package/dist/hosted.d.ts +9 -0
- package/dist/hosted.js +15 -1
- package/dist/server.js +2 -0
- package/dist/tool-policy.d.ts +2 -2
- package/dist/tool-policy.js +3 -0
- package/dist/tools/affiliate.d.ts +3 -0
- package/dist/tools/affiliate.js +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,6 +76,7 @@ iris campaigns --help
|
|
|
76
76
|
| billing | `b` | plans | 1 |
|
|
77
77
|
| channels | `ch` | accounts, availability, send | 3 |
|
|
78
78
|
| credits | `cr` | balance, buy | 2 |
|
|
79
|
+
| affiliate | — | (status, link, sign-ups, and commission totals) | 1 |
|
|
79
80
|
| responder | `r` | status, configure, activity, show, pause, resume, triggers list, triggers add, triggers update, triggers remove, triggers posts | 11 |
|
|
80
81
|
|
|
81
82
|
## MCP Server Configuration
|
|
@@ -127,6 +128,19 @@ Add to `.cursor/mcp.json`:
|
|
|
127
128
|
}
|
|
128
129
|
```
|
|
129
130
|
|
|
131
|
+
## Hosted Endpoint
|
|
132
|
+
|
|
133
|
+
Clients that connect to a remote MCP server can use the hosted endpoint instead of this package:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
https://api.iris-ai.dev/mcp
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- Transport: MCP streamable HTTP, `POST` only.
|
|
140
|
+
- Registry: listed in the [official MCP registry](https://registry.modelcontextprotocol.io) as `io.github.JCLVSH/iris`.
|
|
141
|
+
- Auth: `Authorization: Bearer is_live_...` (the same API key; its scopes apply). `initialize` and `tools/list` answer without a key, so a client can read the tool list before signing up; calling a tool needs the key.
|
|
142
|
+
- Tools: a subset of this package. Credits, API key, webhook, and affiliate tools are only available through the npm package. The class of each hosted tool is listed at https://www.iris-ai.dev/docs#mcp-hosted.
|
|
143
|
+
|
|
130
144
|
## Available Tools
|
|
131
145
|
|
|
132
146
|
### Campaigns
|
|
@@ -298,6 +312,14 @@ Add to `.cursor/mcp.json`:
|
|
|
298
312
|
| ------------------- | --------------------------------- |
|
|
299
313
|
| `get_billing_plans` | List available subscription plans |
|
|
300
314
|
|
|
315
|
+
### Affiliate
|
|
316
|
+
|
|
317
|
+
| Tool | Description |
|
|
318
|
+
| --------------- | ---------------------------------------------------------------------------------------- |
|
|
319
|
+
| `get_affiliate` | Your affiliate status, link, sign-ups, and commission totals, with the terms (stdio only) |
|
|
320
|
+
|
|
321
|
+
CLI: `iris affiliate`.
|
|
322
|
+
|
|
301
323
|
### API Keys
|
|
302
324
|
|
|
303
325
|
| Tool | Description |
|
package/dist/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ import { registerBillingCommands } from "./commands/billing.js";
|
|
|
18
18
|
import { registerChannelCommands } from "./commands/channels.js";
|
|
19
19
|
import { registerCreditCommands } from "./commands/credits.js";
|
|
20
20
|
import { registerResponderCommands } from "./commands/responder.js";
|
|
21
|
+
import { registerAffiliateCommands } from "./commands/affiliate.js";
|
|
21
22
|
const program = new Command()
|
|
22
23
|
.name("iris")
|
|
23
24
|
.description("iris CLI - manage campaigns, contacts, messages, and outreach")
|
|
@@ -52,6 +53,7 @@ try {
|
|
|
52
53
|
registerChannelCommands(program, client);
|
|
53
54
|
registerCreditCommands(program, client);
|
|
54
55
|
registerResponderCommands(program, client);
|
|
56
|
+
registerAffiliateCommands(program, client);
|
|
55
57
|
await program.parseAsync(process.argv);
|
|
56
58
|
}
|
|
57
59
|
catch (error) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { formatOutput, handleError, getFormat } from "../cli-utils.js";
|
|
2
|
+
export function registerAffiliateCommands(program, client) {
|
|
3
|
+
program
|
|
4
|
+
.command("affiliate")
|
|
5
|
+
.description("your affiliate status, link, sign-ups, and commission totals")
|
|
6
|
+
.action(async () => {
|
|
7
|
+
try {
|
|
8
|
+
const result = await client.get("/affiliate");
|
|
9
|
+
formatOutput(result, getFormat(program.opts()));
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
handleError(error);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -8,3 +8,22 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
8
8
|
* opens an event stream that never ends).
|
|
9
9
|
*/
|
|
10
10
|
export declare function serveStatelessMcp(server: McpServer, request: Request): Promise<Response>;
|
|
11
|
+
/**
|
|
12
|
+
* True when a JSON-RPC payload (one message or a batch) only asks who the server is and which
|
|
13
|
+
* tools it has. A host may answer these without a key, so an agent or a directory can see what
|
|
14
|
+
* the server does before signing up. Every message must be on the list: one tool call in a
|
|
15
|
+
* batch makes the whole payload need a key.
|
|
16
|
+
*/
|
|
17
|
+
export declare function isDiscoveryPayload(payload: unknown): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* True when the request's body is a discovery payload. Reads a clone, so the request can still
|
|
20
|
+
* be served afterwards, and stops at MAX_DISCOVERY_BODY_BYTES: a larger body, or a chunked one
|
|
21
|
+
* with no Content-Length, is never buffered whole.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isDiscoveryRequest(request: Request): Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* The API client for a keyless discovery server: it has no key, so every use throws. Listing
|
|
26
|
+
* tools never touches the client; a tool call that reached this server would fail here, before
|
|
27
|
+
* any REST request.
|
|
28
|
+
*/
|
|
29
|
+
export declare function keylessClient<Client extends object>(): Client;
|
package/dist/hosted-transport.js
CHANGED
|
@@ -15,3 +15,70 @@ export async function serveStatelessMcp(server, request) {
|
|
|
15
15
|
await server.connect(transport);
|
|
16
16
|
return transport.handleRequest(request);
|
|
17
17
|
}
|
|
18
|
+
/** JSON-RPC methods that describe the server and change nothing. */
|
|
19
|
+
const DISCOVERY_METHODS = new Set(["initialize", "notifications/initialized", "ping", "tools/list"]);
|
|
20
|
+
/** A handshake is three messages; a longer keyless batch is not a handshake. */
|
|
21
|
+
const MAX_DISCOVERY_BATCH = 10;
|
|
22
|
+
/**
|
|
23
|
+
* True when a JSON-RPC payload (one message or a batch) only asks who the server is and which
|
|
24
|
+
* tools it has. A host may answer these without a key, so an agent or a directory can see what
|
|
25
|
+
* the server does before signing up. Every message must be on the list: one tool call in a
|
|
26
|
+
* batch makes the whole payload need a key.
|
|
27
|
+
*/
|
|
28
|
+
export function isDiscoveryPayload(payload) {
|
|
29
|
+
const messages = Array.isArray(payload) ? payload : [payload];
|
|
30
|
+
if (messages.length === 0 || messages.length > MAX_DISCOVERY_BATCH)
|
|
31
|
+
return false;
|
|
32
|
+
return messages.every((message) => {
|
|
33
|
+
if (typeof message !== "object" || message === null)
|
|
34
|
+
return false;
|
|
35
|
+
const method = message.method;
|
|
36
|
+
return typeof method === "string" && DISCOVERY_METHODS.has(method);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/** A handshake fits in a few hundred bytes; a keyless body past this is not one. */
|
|
40
|
+
const MAX_DISCOVERY_BODY_BYTES = 16_384;
|
|
41
|
+
/**
|
|
42
|
+
* True when the request's body is a discovery payload. Reads a clone, so the request can still
|
|
43
|
+
* be served afterwards, and stops at MAX_DISCOVERY_BODY_BYTES: a larger body, or a chunked one
|
|
44
|
+
* with no Content-Length, is never buffered whole.
|
|
45
|
+
*/
|
|
46
|
+
export async function isDiscoveryRequest(request) {
|
|
47
|
+
const stream = request.clone().body;
|
|
48
|
+
if (!stream)
|
|
49
|
+
return false;
|
|
50
|
+
const reader = stream.getReader();
|
|
51
|
+
const bytes = new Uint8Array(MAX_DISCOVERY_BODY_BYTES);
|
|
52
|
+
let size = 0;
|
|
53
|
+
try {
|
|
54
|
+
for (;;) {
|
|
55
|
+
const { done, value } = await reader.read();
|
|
56
|
+
if (done)
|
|
57
|
+
break;
|
|
58
|
+
if (size + value.byteLength > MAX_DISCOVERY_BODY_BYTES) {
|
|
59
|
+
// Not awaited: cancelling one side of a cloned body only settles once the other
|
|
60
|
+
// side is cancelled too, and the caller is about to answer without reading it.
|
|
61
|
+
void reader.cancel().catch(() => undefined);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
bytes.set(value, size);
|
|
65
|
+
size += value.byteLength;
|
|
66
|
+
}
|
|
67
|
+
return isDiscoveryPayload(JSON.parse(new TextDecoder().decode(bytes.subarray(0, size))));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The API client for a keyless discovery server: it has no key, so every use throws. Listing
|
|
75
|
+
* tools never touches the client; a tool call that reached this server would fail here, before
|
|
76
|
+
* any REST request.
|
|
77
|
+
*/
|
|
78
|
+
export function keylessClient() {
|
|
79
|
+
return new Proxy({}, {
|
|
80
|
+
get(_target, property) {
|
|
81
|
+
throw new Error(`an api key is required (client.${String(property)} was used without one)`);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
package/dist/hosted.d.ts
CHANGED
|
@@ -8,3 +8,12 @@ export interface HostedMcpOptions {
|
|
|
8
8
|
}
|
|
9
9
|
/** One hosted MCP request: a per-request client and server over the stateless transport. */
|
|
10
10
|
export declare function handleHostedMcpRequest(request: Request, options: HostedMcpOptions): Promise<Response>;
|
|
11
|
+
/**
|
|
12
|
+
* One keyless discovery request (initialize, tools/list): the same hosted server and tool list,
|
|
13
|
+
* built on a client that cannot make a REST call. The host checks the request with
|
|
14
|
+
* isDiscoveryRequest first; anything else needs a key and goes to handleHostedMcpRequest.
|
|
15
|
+
*/
|
|
16
|
+
export declare function handleHostedMcpDiscovery(request: Request, options: {
|
|
17
|
+
version: string;
|
|
18
|
+
}): Promise<Response>;
|
|
19
|
+
export { isDiscoveryPayload, isDiscoveryRequest } from "./hosted-transport.js";
|
package/dist/hosted.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IrisClient } from "./client.js";
|
|
2
|
-
import { serveStatelessMcp } from "./hosted-transport.js";
|
|
2
|
+
import { keylessClient, serveStatelessMcp } from "./hosted-transport.js";
|
|
3
3
|
import { createIrisServer } from "./server.js";
|
|
4
4
|
/** One hosted MCP request: a per-request client and server over the stateless transport. */
|
|
5
5
|
export function handleHostedMcpRequest(request, options) {
|
|
@@ -7,3 +7,17 @@ export function handleHostedMcpRequest(request, options) {
|
|
|
7
7
|
const server = createIrisServer({ client, version: options.version, profile: "hosted" });
|
|
8
8
|
return serveStatelessMcp(server, request);
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* One keyless discovery request (initialize, tools/list): the same hosted server and tool list,
|
|
12
|
+
* built on a client that cannot make a REST call. The host checks the request with
|
|
13
|
+
* isDiscoveryRequest first; anything else needs a key and goes to handleHostedMcpRequest.
|
|
14
|
+
*/
|
|
15
|
+
export function handleHostedMcpDiscovery(request, options) {
|
|
16
|
+
const server = createIrisServer({
|
|
17
|
+
client: keylessClient(),
|
|
18
|
+
version: options.version,
|
|
19
|
+
profile: "hosted",
|
|
20
|
+
});
|
|
21
|
+
return serveStatelessMcp(server, request);
|
|
22
|
+
}
|
|
23
|
+
export { isDiscoveryPayload, isDiscoveryRequest } from "./hosted-transport.js";
|
package/dist/server.js
CHANGED
|
@@ -18,6 +18,7 @@ import { registerUsageTools } from "./tools/usage.js";
|
|
|
18
18
|
import { registerAutopilotTools } from "./tools/autopilot.js";
|
|
19
19
|
import { registerResponderTools } from "./tools/responder.js";
|
|
20
20
|
import { registerAudienceTools } from "./tools/audiences.js";
|
|
21
|
+
import { registerAffiliateTools } from "./tools/affiliate.js";
|
|
21
22
|
export function createIrisServer({ client, version, profile }) {
|
|
22
23
|
const server = new McpServer({ name: "iris", version });
|
|
23
24
|
const registrar = createPolicyRegistrar(server, TOOL_POLICY, profile);
|
|
@@ -36,6 +37,7 @@ export function createIrisServer({ client, version, profile }) {
|
|
|
36
37
|
registerAgentCreditTools(registrar, client);
|
|
37
38
|
registerUsageTools(registrar, client);
|
|
38
39
|
registerAutopilotTools(registrar, client);
|
|
40
|
+
registerAffiliateTools(registrar, client);
|
|
39
41
|
registerResponderTools(registrar, client);
|
|
40
42
|
registerAudienceTools(registrar, client);
|
|
41
43
|
return server;
|
package/dist/tool-policy.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type PolicyTable, type ToolClass, type ToolPolicy, type ToolProfile } from "./policy.js";
|
|
2
2
|
export type { ToolClass, ToolPolicy, ToolProfile };
|
|
3
|
-
export declare const TOOL_CATEGORIES: readonly ["campaigns", "contacts", "conversations", "messages", "channels", "discovery", "audiences", "autopilot", "responder", "ai", "analytics", "notifications", "usage", "billing", "credits", "API keys", "webhooks"];
|
|
3
|
+
export declare const TOOL_CATEGORIES: readonly ["campaigns", "contacts", "conversations", "messages", "channels", "discovery", "audiences", "autopilot", "responder", "ai", "analytics", "notifications", "usage", "billing", "credits", "affiliate", "API keys", "webhooks"];
|
|
4
4
|
export type ToolCategory = (typeof TOOL_CATEGORIES)[number];
|
|
5
5
|
export declare const TOOL_POLICY: PolicyTable<ToolCategory>;
|
|
6
|
-
export declare const toolPolicy: (name: string) => ToolPolicy<"campaigns" | "contacts" | "usage" | "messages" | "conversations" | "discovery" | "ai" | "webhooks" | "notifications" | "analytics" | "billing" | "channels" | "credits" | "responder" | "audiences" | "autopilot" | "API keys">;
|
|
6
|
+
export declare const toolPolicy: (name: string) => ToolPolicy<"campaigns" | "contacts" | "usage" | "messages" | "conversations" | "discovery" | "ai" | "webhooks" | "notifications" | "analytics" | "billing" | "channels" | "credits" | "responder" | "affiliate" | "audiences" | "autopilot" | "API keys">;
|
|
7
7
|
export declare const isToolEnabled: (name: string, profile: ToolProfile) => boolean;
|
|
8
8
|
export declare const toolAnnotations: (name: string) => {
|
|
9
9
|
readOnlyHint: boolean;
|
package/dist/tool-policy.js
CHANGED
|
@@ -15,6 +15,7 @@ export const TOOL_CATEGORIES = [
|
|
|
15
15
|
"usage",
|
|
16
16
|
"billing",
|
|
17
17
|
"credits",
|
|
18
|
+
"affiliate",
|
|
18
19
|
"API keys",
|
|
19
20
|
"webhooks",
|
|
20
21
|
];
|
|
@@ -126,6 +127,8 @@ export const TOOL_POLICY = {
|
|
|
126
127
|
get_usage: hosted("usage", "read"),
|
|
127
128
|
get_changelog: hosted("usage", "read"),
|
|
128
129
|
get_billing_plans: hosted("billing", "read"),
|
|
130
|
+
// affiliate program (hosted tool set is frozen: npm package only)
|
|
131
|
+
get_affiliate: localOnly("affiliate", "read"),
|
|
129
132
|
// credits (agent payments: never hosted)
|
|
130
133
|
get_credit_balance: localOnly("credits", "read"),
|
|
131
134
|
purchase_credit_pack: localOnly("credits", "sensitive"),
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { formatMcpError } from "../cli-utils.js";
|
|
2
|
+
export function registerAffiliateTools(server, client) {
|
|
3
|
+
server.tool("get_affiliate", 'the user\'s affiliate status ("none" if they have not applied), code and link, sign-up count, commission totals in US cents (in the hold, payable, paid out), and the program terms. read-only; applying is done in the app at /settings/affiliate. requires read scope.', {}, async () => {
|
|
4
|
+
try {
|
|
5
|
+
const result = await client.get("/affiliate");
|
|
6
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
return formatMcpError(error);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
}
|