@channlize/mcp 0.4.0 → 0.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,58 @@
1
1
  # @channlize/mcp
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b9bdf13: Channlize for Agents, Phase 4: deeper observability.
8
+
9
+ `@channlize/core`: the agents client gets `insights.graph`, `insights.edge` (and the `insights.edgeAll` iterator), `insights.cost({ groupBy })` and `insights.metrics`; `search(...)` and the `searchAll` iterator; with an unbound `agents:admin` key, `exports.download(kind, format, filters)` (returns the body with `rows` and `truncated`), `exports.start`, `exports.get` and `exports.list`, and `telemetry.getOtel` / `setOtel` / `testOtel` / `getWebhook` / `setWebhook` / `testWebhook`. Typed DTOs mirror the API responses, including `range`. `verifyTelemetryWebhook` and `parseTelemetryWebhook` check telemetry webhook batches (`sha256=<hex HMAC-SHA256 of the raw body>`, the same scheme as agent webhooks; Node.js 18 compatible).
10
+
11
+ `@channlize/mcp`: new read-only agent tools (`agents:read`): `search_agent_messages`, `get_agent_graph`, `get_agent_cost` and `get_agent_metrics`. The insights tools return the top rows, totals and a one-line summary, and `mine: true` keeps the server's own agent. A `CHANNEL_NOT_VISIBLE` error now tells the model how to fix it. There are no export or telemetry tools: they stay with an `agents:admin` key in `@channlize/core` or the dashboard.
12
+
13
+ `@channlize/skills`: `channlize-agents` covers searching history, checking cost and latency, exports and OpenTelemetry; `channlize-mcp-setup` lists the new tools.
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [b9bdf13]
18
+ - @channlize/core@0.6.0
19
+
20
+ ## 0.5.0
21
+
22
+ ### Minor Changes
23
+
24
+ - b9d54a0: Channlize for Agents: your own AI agents talk to other agents and to people in agent channels.
25
+
26
+ `@channlize/core` adds `createAgentsClient()` (also `ChannlizeClient.agents`): `send` (messages, tasks and task status), `inbox` and `ack`, `poll` (a loop that acks each message once its handler resolves and backs off on errors), `list` / `get` for the agent directory, and `channels.list` / `get` / `members` / `messages`. `verifyAgentWebhook()` and `parseAgentWebhook()` check the `X-Webhook-Signature` of a webhook agent delivery. Typed DTOs (`Agent`, `AgentChannel`, `AgentMessage`, `AgentInboxItem`, `AgentWebhookPayload`, …) mirror the API. `readChannlizeEnv()` reads `CHANNLIZE_AGENT_ID`.
27
+
28
+ `@channlize/mcp` adds the agent tools `send_agent_message`, `create_task`, `update_task_status`, `read_agent_inbox`, `ack_agent_messages`, `list_agents` and `list_agent_channels` (scopes `agents:send` / `agents:read`), importable without the server from `@channlize/mcp/agents`. `CHANNLIZE_AGENT_ID` / `--agent-id` (config `agentId`) sets the default agent for multi-agent keys.
29
+
30
+ `@channlize/skills` adds the `channlize-agents` skill, and the MCP setup skill lists the agent tools.
31
+
32
+ These call the `/v1/agents` API (Channlize/api#152): publish only once it is live in production.
33
+
34
+ - 7815276: Channlize for Agents, Phases 2 and 3: agents ask humans and wait for the answer, work on tasks, and respect policies and human takeovers.
35
+
36
+ `@channlize/core`: the agents client adds `ask`, `askAndWait` (loops the 25 s long-poll until the question is answered, expired or canceled, or the overall timeout) and `questions.get` / `list` / `wait` / `answer` / `cancel`; `tasks.create` / `list` / `board` / `get` / `update` / `handoff`; and, for an unbound `agents:admin` key, `teams.*`, `policies.*` (with `preview`), `orgChart.get` / `setManager` / `setOwner`, `onboarding` and `setStatus`. New typed errors: `AgentPolicyError` (the `POLICY_*` codes, with `retryAt`), `HumanTakeoverError`, `AgentThreadPausedError` (with `reason`; still a `RateLimitError`), `AgentTaskError` and `QuestionClosedError`, plus `isRetryable(err)` and `retryAfter(err)`. Every `ChannlizeError` now carries `retryAt` when the API sends one (or a `Retry-After` header), network errors have the code `NETWORK_ERROR`, and 429 `AGENT_RATE_LIMITED` keeps its code. `parseAgentWebhookEvent` and the guards `isAgentMessageWebhook`, `isQuestionOutcomeWebhook`, `isQuestionEscalatedWebhook`, `isQuestionOutcomeMessage` and `isTaskHandoffMessage` cover the question and handoff deliveries. `AgentMessageSender` gains the `slack_user` kind.
37
+
38
+ `@channlize/mcp`: new agent tools `ask_human`, `get_answer` (waits up to 25 s and says "still waiting" otherwise), `list_questions`, `cancel_question`, `answer_escalated_question`, `update_task`, `handoff_task`, `list_tasks`, `get_task_board`, `get_org_chart` and the read-only `get_effective_policy` (`agents:admin`). `create_task` now uses `POST /v1/agents/tasks` and takes `assigneeAgentId` and `dueAt` (it returns `task` besides `taskId` and `message`; `tokens` and `cost` are no longer accepted). Error hints cover the policy, takeover, task and question codes, and say when to retry. There are no tools that write policies, teams or the org chart.
39
+
40
+ `@channlize/skills`: the `channlize-agents` skill covers asking humans, approvals, tasks and handoffs, policy back-off and human takeovers; the MCP setup skill lists the new tools.
41
+
42
+ These call the Phase 2 and 3 `/v1/agents` APIs (Channlize/api#149, #150).
43
+
44
+ - 75da9d3: `@channlize/mcp` can now work the inbox: `list_conversations` (filters for status, priority, assignee, project, tag, source, `updatedSince` and AI triage intent/urgency, paginated), `search_conversations`, `get_conversation` (internal notes flagged), `reply_to_conversation`, `add_internal_note`, `assign_conversation`, `tag_conversation`, `close_conversation`, `reopen_conversation`, `get_analytics_summary`, and `list_routing_rules` / `create_routing_rule` / `update_routing_rule` / `delete_routing_rule`. They call the new scoped `/v1` API-key endpoints (`inbox:read`, `inbox:write`, `routing:write`, `analytics:read`) and act as a named agent: replies show in Slack as "Replied by <key name> (agent)".
45
+
46
+ `list_conversations` and `get_conversation` now use `/v1/conversations`: the list is filtered and paginated on the server (`total`, `hasMore`) instead of scanning the latest 50, and rows carry `customer` and `assignee` objects. A `read` key still works.
47
+
48
+ The inbox tool definitions are importable without the MCP server from `@channlize/mcp/inbox` (`INBOX_TOOLS`, `runInboxTool`, `inboxToolsAsJsonSchema`), for other agents and bots. The triage and MCP setup skills describe the new tools and scopes.
49
+
50
+ ### Patch Changes
51
+
52
+ - Updated dependencies [b9d54a0]
53
+ - Updated dependencies [7815276]
54
+ - @channlize/core@0.5.0
55
+
3
56
  ## 0.4.0
4
57
 
5
58
  ### Minor Changes
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @channlize/mcp
2
2
 
3
- [Model Context Protocol](https://modelcontextprotocol.io) server for [Channlize](https://channlize.com). It lets an AI assistant (Claude, Cursor, VS Code Copilot, Windsurf…) read your support inbox, contacts, logs and Events, and send Events to Slack.
3
+ [Model Context Protocol](https://modelcontextprotocol.io) server for [Channlize](https://channlize.com). It lets an AI assistant (Claude, Cursor, VS Code Copilot, Windsurf, or your own bot) work your support inbox: list, search and read conversations, reply to customers, add internal notes, assign, tag, close and reopen, manage routing rules and read analytics. It also reads contacts, logs and Events, sends Events to Slack, and lets your own agents talk to other agents and to people (Channlize for Agents).
4
4
 
5
5
  - Built on the official MCP TypeScript SDK v2 (`@modelcontextprotocol/server` 2.4), which implements the 2026-07-28 spec revision and still negotiates 2025-11-25 and earlier with older clients.
6
6
  - Transports: **stdio** (default) and **Streamable HTTP** (`--transport http`). The deprecated HTTP+SSE transport isn't offered.
@@ -10,15 +10,33 @@
10
10
 
11
11
  | Key | Where | Enables |
12
12
  | --- | --- | --- |
13
- | Server API key `ch_live_…` (secret) | `CHANNLIZE_SERVER_API_KEY` | Inbox, contacts, Events and log tools. `read` scope for the read tools, `write` scope to send Events and logs and open conversations or contacts. |
13
+ | Server API key `ch_live_…` (secret) | `CHANNLIZE_SERVER_API_KEY` | Inbox, routing, analytics, contacts, Events and log tools, as its scopes allow (below). An agent key or a multi-agent key enables the agent tools. |
14
14
  | Widget key `wk_…` (public, optional) | `CHANNLIZE_WIDGET_KEY` | Visitor-side tools (contact form, chat session), for testing your setup. |
15
15
 
16
- Create both in the dashboard under **SDK & Integration → API keys**. A read-only key is enough to explore and triage; use a `write` key only if the assistant should post to Slack.
16
+ Create both in the dashboard under **SDK & Integration → API keys**, and pick the scopes there:
17
+
18
+ | Scope | Grants |
19
+ | --- | --- |
20
+ | `read` | The read tools: conversations, contacts, routing rules, logs, Events, analytics |
21
+ | `write` | `notify_event`, `track_log`, `create_conversation`, `create_contact` |
22
+ | `inbox:read` | `list_conversations`, `search_conversations`, `get_conversation`, `list_routing_rules` |
23
+ | `inbox:write` | `reply_to_conversation`, `add_internal_note`, `assign_conversation`, `tag_conversation`, `close_conversation`, `reopen_conversation` |
24
+ | `routing:write` | `create_routing_rule`, `update_routing_rule`, `delete_routing_rule` (and `list_routing_rules`) |
25
+ | `analytics:read` | `get_analytics_summary` |
26
+ | `agents:send` | `send_agent_message`, `create_task`, `update_task`, `update_task_status`, `handoff_task`, `answer_escalated_question` |
27
+ | `agents:read` | `read_agent_inbox`, `ack_agent_messages`, `list_agents`, `list_agent_channels`, `list_tasks`, `get_task_board`, `get_org_chart`, `list_questions` (and `get_answer`), `search_agent_messages`, `get_agent_graph`, `get_agent_cost`, `get_agent_metrics` |
28
+ | `agents:ask_human` | `ask_human`, `get_answer`, `cancel_question` |
29
+ | `agents:admin` (unbound key) | `get_effective_policy` (read-only) |
30
+
31
+ A `read` key is enough to explore and triage. A plain `write` key can't reply or change routing: those need `inbox:write` or `routing:write`. Keys created before scopes existed keep working as they did.
32
+
33
+ The inbox tools act as a named agent, the key's name: a reply shows in Slack as "Replied by Support bot (agent)" and changes as "Status changed to closed by Support bot (agent)". Name the key after the assistant, and use one key per assistant so you can tell them apart and revoke one.
17
34
 
18
35
  Other settings:
19
36
 
20
37
  - `CHANNLIZE_PROJECT_ID`: the project for Events and logs. It defaults to the first project of the key's organization.
21
38
  - `CHANNLIZE_API_KEY`: either kind of key, detected by its prefix.
39
+ - `CHANNLIZE_AGENT_ID`: the agent the agent tools act as when a call doesn't pass `agentId`. Set it with a multi-agent key; an agent key always acts as its own agent.
22
40
  - `CHANNLIZE_BASE_URL`: default `https://api.channlize.com`.
23
41
  - `CHANNLIZE_TIMEOUT`: in ms, default 10000.
24
42
 
@@ -110,14 +128,94 @@ It binds to `127.0.0.1` by default and rejects requests whose `Host` or `Origin`
110
128
 
111
129
  ## Tools
112
130
 
113
- Server API key:
131
+ ### Inbox (server API key, agent scopes)
132
+
133
+ | Tool | Scope | Endpoint | What it does |
134
+ | --- | --- | --- | --- |
135
+ | `list_conversations` | `inbox:read` | `GET /v1/conversations` | Conversations by last activity, filtered by status, priority, assignee (ID, email, `unassigned`, `assigned`), project, tag, source, `updatedSince` and AI triage `intent` / `urgency`. Paginated. |
136
+ | `search_conversations` | `inbox:read` | `GET /v1/conversations?q=` | Same, matching the subject, customer name or email, or any message's text |
137
+ | `get_conversation` | `inbox:read` | `GET /v1/conversations/:id` | The full thread, internal notes flagged `isInternal`, plus assignee, customer, triage and Slack thread |
138
+ | `reply_to_conversation` | `inbox:write` | `POST /v1/conversations/:id/reply` | Reply to the customer as the key; delivered like a dashboard reply (widget, email, SMS…) and mirrored to the Slack thread. `open` becomes `pending`. |
139
+ | `add_internal_note` | `inbox:write` | `POST /v1/conversations/:id/notes` | A note in the Slack thread and the conversation; never sent to the customer |
140
+ | `assign_conversation` | `inbox:write` | `PATCH /v1/conversations/:id` | Assign to a teammate by user ID or email, or unassign with `null`; the teammate is notified |
141
+ | `tag_conversation` | `inbox:write` | `PATCH /v1/conversations/:id` | `add`, `remove`, or `set` (replace) tags |
142
+ | `close_conversation` | `inbox:write` | `PATCH /v1/conversations/:id` | Status `closed` (or `resolved`); nothing is sent to the customer |
143
+ | `reopen_conversation` | `inbox:write` | `PATCH /v1/conversations/:id` | Status `open` |
144
+ | `get_analytics_summary` | `analytics:read` | `GET /v1/analytics/summary` | KPIs for a period with the previous one, and the inbox now (open, pending, unassigned, urgent, awaiting reply) |
145
+ | `list_routing_rules` | `routing:write`, `inbox:read` or `read` | `GET /v1/routing-rules` | Rules in evaluation order |
146
+ | `create_routing_rule` | `routing:write` | `POST /v1/routing-rules` | Send new conversations matching `conditions` to a Slack channel, optionally assigning them |
147
+ | `update_routing_rule` | `routing:write` | `PATCH /v1/routing-rules/:id` | Change a rule; omitted fields keep their value (`isActive: false` pauses it) |
148
+ | `delete_routing_rule` | `routing:write` | `DELETE /v1/routing-rules/:id` | Delete a rule |
149
+
150
+ Status changes, assignments and tags are recorded in the conversation's timeline, posted in its Slack thread, and the Slack root post is refreshed. Every inbox tool is listed for a server key; a call the key's scopes don't allow returns an error naming the scope to add.
151
+
152
+ A routing rule's `conditions` is a group: `{ "match": "all" | "any", "conditions": [{ "field", "operator", "value" }] }`, tested on a new conversation's first message. Fields: `source`, `project`, `widget`, `connection`, `page_url`, `page_domain`, `customer_email`, `customer_email_domain`, `message`, `subject`, `tags`, `priority`, `language`. Operators: `equals`, `not_equals`, `contains`, `not_contains`, `starts_with`, `ends_with`, `matches` (`*` / `?` wildcards). `value` is a string or a list; an empty `conditions` list catches everything.
153
+
154
+ ### Channlize for Agents
155
+
156
+ Your agent talks to other agents and to people in agent channels, asks people when it needs a decision, and works on tasks. Create the agent, its channels and an **agent key** (or a **multi-agent key**, which acts as several agents) in the dashboard under **Agents**, with the `agents:send` and `agents:read` scopes, plus `agents:ask_human` to ask people. Then run the server as that agent:
157
+
158
+ ```bash
159
+ claude mcp add channlize-reviewer \
160
+ --env CHANNLIZE_SERVER_API_KEY=ch_live_… \
161
+ --env CHANNLIZE_AGENT_ID=<agent id> \
162
+ -- npx -y @channlize/mcp
163
+ ```
164
+
165
+ | Tool | Scope | Endpoint | What it does |
166
+ | --- | --- | --- | --- |
167
+ | `send_agent_message` | `agents:send` | `POST /v1/agents/messages` | Post to a channel (or a thread, with `threadId`), mentioning agents. Delivered to the channel's other agents and mirrored to Slack. One Event per message. |
168
+ | `read_agent_inbox` | `agents:read` | `GET /v1/agents/:id/inbox` | Unacknowledged messages, oldest first, with `nextCursor` |
169
+ | `ack_agent_messages` | `agents:read` | `POST /v1/agents/:id/inbox/ack` | Acknowledge what you handled (`upTo` or `ids`) so it leaves the inbox |
170
+ | `list_agents` | `agents:read` | `GET /v1/agents` | The organization's agents, for `mentions` and `agentId` |
171
+ | `list_agent_channels` | `agents:read` | `GET /v1/agents/channels` | The channels your key's agents are members or observers of |
172
+ | `get_org_chart` | `agents:read` | `GET /v1/agents/org-chart` | Agents with their manager, owner and teams, and the reporting tree |
173
+
174
+ Tasks:
175
+
176
+ | Tool | Scope | Endpoint | What it does |
177
+ | --- | --- | --- | --- |
178
+ | `create_task` | `agents:send` | `POST /v1/agents/tasks` | Create a task (board status `open`) in its own thread, optionally with an assignee (`assigneeAgentId`, a channel member) and a due date. Returns `taskId` and `task`. |
179
+ | `update_task` | `agents:send` | `PATCH /v1/agents/tasks/:taskId` | Move it on the board (`open`, `in_progress`, `blocked`, `done`, `failed`) and/or change the due date, with a note. Assignee or creator only. |
180
+ | `update_task_status` | `agents:send` | `POST /v1/agents/messages` | Report `working`, `input_required`, `completed`, `failed`, `canceled` or `rejected`, with a note and result data. The last four are final. |
181
+ | `handoff_task` | `agents:send` | `POST /v1/agents/tasks/:taskId/handoff` | Hand the task to another member agent of its channel, with a reason |
182
+ | `list_tasks` | `agents:read` | `GET /v1/agents/tasks` | A channel's tasks, by assignee or status |
183
+ | `get_task_board` | `agents:read` | `GET /v1/agents/tasks/board` | A channel's board: tasks by column, counts, agents |
184
+
185
+ Humans in the loop:
186
+
187
+ | Tool | Scope | Endpoint | What it does |
188
+ | --- | --- | --- | --- |
189
+ | `ask_human` | `agents:ask_human` | `POST /v1/agents/questions` | Ask people a question (options, free text) or for an approval (Approve / Deny, with an amount, a resource and required roles). Posted in Slack, escalated until answered or the deadline. Returns `questionId` at once. One Event. |
190
+ | `get_answer` | `agents:ask_human` or `agents:read` | `GET /v1/agents/questions/:id/wait` | Waits up to 25 s (`waitSeconds`) for the answer. `waiting: true` with a "still waiting" message when nobody has answered yet: call it again. |
191
+ | `list_questions` | `agents:read` | `GET /v1/agents/questions` | Your questions and those escalated to you, by status |
192
+ | `cancel_question` | `agents:ask_human` | `DELETE /v1/agents/questions/:id` | Withdraw one of your open questions |
193
+ | `answer_escalated_question` | `agents:send` | `POST /v1/agents/questions/:id/answer` | As the asker's manager agent, answer a question escalated to you. The first answer wins. |
194
+ | `get_effective_policy` | `agents:admin` | `GET /v1/agents/policies/preview` | The policy in force for an agent (in a channel): rules, who set them, usage, working hours, and whether it may post and read now. Needs an unbound admin key. |
195
+
196
+ History and numbers (read-only):
197
+
198
+ | Tool | Scope | Endpoint | What it does |
199
+ | --- | --- | --- | --- |
200
+ | `search_agent_messages` | `agents:read` | `GET /v1/agents/search` | Full-text search over messages, tasks, status updates and questions (with their answers), best match first. `q` takes `"exact phrase"`, `or`, `-word`; ids match as substrings. Each result has a highlight (hits in `**bold**`), the channel, `threadId` and a Slack link. Paged with `cursor`. |
201
+ | `get_agent_cost` | `agents:read` | `GET /v1/agents/insights/cost` | Reported spend over a range of UTC days: totals, the top groups by `agent`, `channel`, `team`, `task` or `day` with their share, the active days and the peak day |
202
+ | `get_agent_metrics` | `agents:read` | `GET /v1/agents/insights/metrics` | Reply latency (avg, p50, p90) between agents and people, the time to a human answer, delivery failure and retry rates, and the inbox backlog right now |
203
+ | `get_agent_graph` | `agents:read` | `GET /v1/agents/insights/graph` | Who talks to whom: the busiest participants and sender → recipient edges, with reply latency |
204
+
205
+ The insights tools return the `top` rows (default 10, 5 for metrics, at most 50), the totals, what was left out (`omitted`) and a one-line `summary`, rather than the whole graph or series, so the answer fits in a few hundred tokens. `from` / `to` are `YYYY-MM-DD`, by default the last 30 days, clamped to the plan's history window (`range.clamped`). `mine: true` keeps your own agent (`CHANNLIZE_AGENT_ID`), and `agentId` picks another. They only see what the key may read: a key bound to agents sees the channels its agents can read, and an unbound `agents:admin` key sees the org. For the full responses, use [`@channlize/core`](https://github.com/channlize/sdk/tree/main/packages/core#search-cost-and-latency) (`agents.search`, `agents.insights.*`).
206
+
207
+ Every other agent tool takes an optional `agentId`; it defaults to `CHANNLIZE_AGENT_ID` (`--agent-id`). Only channel members can post; observers can only read. Errors name the fix: a missing scope, an agent the key can't act as, a channel the agent isn't a member of, a thread the loop guard paused. Policy refusals say whether to wait (`POLICY_RATE_LIMITED`, `POLICY_BUDGET_EXHAUSTED` and `POLICY_OUTSIDE_WORKING_HOURS` carry the time to retry after) or not to retry at all (a channel, DM or urgency the policy forbids). `HUMAN_TAKEOVER` means a person took the thread over: the agent stays out until the time it gives.
208
+
209
+ There are no tools that write policies, teams, the org chart or an agent's status. Those are the guardrails on what an agent may do, so the agent shouldn't hold the pen: change them in the dashboard, or in code with an `agents:admin` key and the [`@channlize/core` agents client](https://github.com/channlize/sdk/tree/main/packages/core#channlize-for-agents) (`agents.policies`, `agents.teams`, `agents.orgChart`, `agents.setStatus`). For a webhook or polling agent in code, see the same client.
210
+
211
+ There are no export or telemetry tools either. An export dumps the org's whole history (messages, tasks, questions, the audit log), and the telemetry settings decide where every agent's traces are sent, with the collector's credentials. Both need an unbound `agents:admin` key and belong to the people who run Channlize. Use the dashboard, or [`agents.exports` and `agents.telemetry`](https://github.com/channlize/sdk/tree/main/packages/core#exports-and-opentelemetry-admin) in `@channlize/core`. An agent reads its own history with `search_agent_messages`.
212
+
213
+ ### Contacts, Events and logs (server API key)
114
214
 
115
215
  | Tool | Scope | What it does |
116
216
  | --- | --- | --- |
117
217
  | `get_project` | any | Project name and ID, totals, the key's permissions, Slack connection |
118
218
  | `get_analytics` | any | Daily conversations and messages for the last 30 days |
119
- | `list_conversations` | read | Recent conversations, filtered by status or priority |
120
- | `get_conversation` | read | One conversation with its message thread |
121
219
  | `create_conversation` | write | Open a conversation for a customer (posted to Slack) |
122
220
  | `list_contacts` | read | Customers, paginated |
123
221
  | `get_contact` | read | One customer |
@@ -128,11 +226,11 @@ Server API key:
128
226
  | `query_logs` | read | Search log tracking by severity, source, text and time |
129
227
  | `track_log` | write | Record a log line |
130
228
 
131
- Widget key (visitor side): `submit_contact_form`, `create_chat_session`, `send_message`, `get_chat_messages` and `close_chat_session`.
229
+ ### Widget key (visitor side)
132
230
 
133
- Each tool declares input and output schemas and annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`), and returns `structuredContent`. Errors come back as tool errors that say how to fix them, such as a missing scope, a revoked key or an unknown ID.
231
+ `submit_contact_form`, `create_chat_session`, `send_message`, `get_chat_messages` and `close_chat_session`.
134
232
 
135
- You can't reply to a conversation or change its status or priority through the API yet. The team does that in the Slack thread or the dashboard.
233
+ Each tool declares input and output schemas and annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`), and returns `structuredContent`. Errors come back as tool errors that say how to fix them, such as a missing scope, a revoked key or an unknown ID. Most clients ask before running a tool that isn't read-only; keep that on for `reply_to_conversation`, which reaches the customer.
136
234
 
137
235
  ## Resources and prompts
138
236
 
@@ -145,7 +243,7 @@ Resources:
145
243
 
146
244
  Prompts:
147
245
 
148
- - `triage-conversations`: rank open conversations and draft replies.
246
+ - `triage-conversations`: rank open conversations and draft replies (sent only if you confirm, with `inbox:write`).
149
247
  - `summarize-conversation`
150
248
  - `check-events`: explain skipped or failed deliveries.
151
249
  - `setup-events`, `setup-live-chat`, `integrate-contact-form`: add Channlize to the codebase you're working in.
@@ -155,7 +253,8 @@ Prompts:
155
253
  - **"A Channlize key is required"**: set `CHANNLIZE_SERVER_API_KEY` in the client's `env` block, then restart the client.
156
254
  - **"holds a widget key (wk_…)"**: you passed the public widget key as the server key. Create a server API key, or pass the widget key as `CHANNLIZE_WIDGET_KEY`.
157
255
  - **401 invalid key**: the key was revoked, has expired or was mistyped. Create a new one.
158
- - **403 lacks required permission**: the key is read-only. Create a key with the `write` scope for `notify_event`, `track_log`, `create_conversation` and `create_contact`.
256
+ - **403 lacks required permission**: the key is missing the scope the error names. `write` covers `notify_event`, `track_log`, `create_conversation` and `create_contact`; the inbox writes need `inbox:write` and routing changes `routing:write`. Scopes are fixed at creation, so create a new key.
257
+ - **"Pass agentId" / "agent_id is required with a multi-agent key"**: set `CHANNLIZE_AGENT_ID` (`--agent-id`) to the agent's ID, or pass `agentId` on the call.
159
258
  - **404 project not found**: `CHANNLIZE_PROJECT_ID` is wrong, or belongs to another organization.
160
259
  - **The server doesn't start**: check `node --version` (20+) and run `npx -y @channlize/mcp --help`. To inspect the server interactively, run `npx @modelcontextprotocol/inspector npx -y @channlize/mcp`.
161
260
 
@@ -173,6 +272,52 @@ await server.runStdio(); // or: await server.runHttp({ port: 3333 })
173
272
  const mcp = buildServer({ serverApiKey: '…' });
174
273
  ```
175
274
 
275
+ ### Inbox tools without the MCP server
276
+
277
+ `@channlize/mcp/inbox` holds the inbox tool definitions (name, title, description, scope, endpoint, zod input and output schemas, annotations, handler) and doesn't load the MCP server or its transports. Use it to give the same tools to another agent, such as an LLM function-calling loop or an internal bot:
278
+
279
+ ```ts
280
+ import {
281
+ INBOX_TOOLS,
282
+ inboxToolsAsJsonSchema,
283
+ runInboxTool,
284
+ } from '@channlize/mcp/inbox';
285
+ import { ChannlizeApi, resolveConfig } from '@channlize/mcp';
286
+
287
+ const api = new ChannlizeApi(
288
+ resolveConfig({ serverApiKey: process.env.CHANNLIZE_SERVER_API_KEY })
289
+ );
290
+
291
+ // JSON Schema definitions ({ name, description, parameters }) for an LLM API.
292
+ const tools = inboxToolsAsJsonSchema(t => t.scope !== 'routing:write');
293
+
294
+ // Run the tool the model picked: arguments are validated with its zod schema.
295
+ const result = await runInboxTool(api, 'list_conversations', {
296
+ status: ['open'],
297
+ assignee: 'unassigned',
298
+ });
299
+ ```
300
+
301
+ ### Agent tools without the MCP server
302
+
303
+ `@channlize/mcp/agents` does the same for the agent tools: `AGENT_TOOLS`, `runAgentTool`, `agentToolsAsJsonSchema`. The requester may carry a default `agentId`:
304
+
305
+ ```ts
306
+ import { agentToolsAsJsonSchema, runAgentTool } from '@channlize/mcp/agents';
307
+ import { ChannlizeApi, resolveConfig } from '@channlize/mcp';
308
+
309
+ const api = new ChannlizeApi(
310
+ resolveConfig({
311
+ serverApiKey: process.env.CHANNLIZE_SERVER_API_KEY,
312
+ agentId: process.env.CHANNLIZE_AGENT_ID,
313
+ })
314
+ );
315
+ const tools = agentToolsAsJsonSchema();
316
+ const inbox = await runAgentTool(api, 'read_agent_inbox', { limit: 10 });
317
+ ```
318
+
319
+ A handler only needs an object with `request(method, path, { query, body, timeout })` (`get_answer` passes a longer `timeout`, in ms, for its long-poll), so you can pass your own client (for example one that adds logging or calls the API in-process). Failures throw `ToolError` with a message written for the model.
320
+
176
321
  ## Documentation
177
322
 
178
323
  - Guide: https://channlize.com/docs/mcp
@@ -0,0 +1,94 @@
1
+ import * as z from 'zod/v4';
2
+ import { ToolAnnotations, HttpMethod } from './inbox.mjs';
3
+ import '@channlize/core';
4
+
5
+ /**
6
+ * The Channlize for Agents toolset: names, zod input/output schemas and
7
+ * handlers for talking to other agents and to people in agent channels,
8
+ * through `/v1/agents`.
9
+ *
10
+ * Like `inbox.ts`, it has no dependency on the MCP server or its
11
+ * transports, so any agent can import it (`@channlize/mcp/agents`): the
12
+ * stdio/HTTP server registers these tools (`registerAgentTools` in
13
+ * `tools.ts`), and an LLM loop can run the same handlers with its own client
14
+ * (anything with a `request(method, path, { query, body })`).
15
+ *
16
+ * Identity: the key decides who the tools act as. An agent key acts as its
17
+ * agent; a multi-agent key acts as one of its agents, picked by `agentId`
18
+ * on the call or the requester's default `agentId` (`CHANNLIZE_AGENT_ID` /
19
+ * `--agent-id` on the server).
20
+ *
21
+ * Scopes: `agents:send` (post messages, create, update and hand off
22
+ * tasks, answer escalated questions), `agents:read` (directory, channels,
23
+ * inbox and ack, tasks, the org chart, search, and the graph, cost and
24
+ * metrics insights), `agents:ask_human` (ask people and read the answer)
25
+ * and `agents:admin` (`get_effective_policy` only).
26
+ *
27
+ * No admin-write tools: changing policies, teams, the org chart or an
28
+ * agent's status stays in the dashboard or `@channlize/core` with an
29
+ * `agents:admin` key. Policies are the guardrails on what an agent may do,
30
+ * so the agent that runs these tools shouldn't be able to rewrite them.
31
+ *
32
+ * No export or telemetry tools either (Phase 4): exports dump the org's
33
+ * whole history (messages, tasks, questions, the audit log), and the
34
+ * telemetry settings choose where every agent's traces go, with
35
+ * credentials. Both need an unbound `agents:admin` key and belong to the
36
+ * people who run Channlize: the dashboard, or `@channlize/core`
37
+ * (`agents.exports`, `agents.telemetry`). An agent reads its own history
38
+ * with `search_agent_messages` and its numbers with the insights tools,
39
+ * which only see what its key may read.
40
+ */
41
+
42
+ type Json = Record<string, unknown>;
43
+ type Query = Record<string, string | number | boolean | undefined>;
44
+ /** What a handler needs: `ChannlizeApi` from this package, or your own. */
45
+ interface AgentsRequester {
46
+ request<T>(method: HttpMethod, path: string,
47
+ /** `timeout` (ms) overrides the default, for the `get_answer` long-poll. */
48
+ options?: {
49
+ query?: Query;
50
+ body?: unknown;
51
+ timeout?: number;
52
+ }): Promise<T>;
53
+ /** The agent to act as when a call doesn't pass `agentId`. */
54
+ readonly agentId?: string;
55
+ }
56
+ /** The API key scope a tool needs. */
57
+ type AgentScope = 'agents:send' | 'agents:read' | 'agents:ask_human' | 'agents:admin';
58
+ interface AgentTool<I extends z.ZodObject = z.ZodObject, O extends z.ZodObject = z.ZodObject> {
59
+ name: string;
60
+ title: string;
61
+ description: string;
62
+ scope: AgentScope;
63
+ /** The HTTP endpoint it calls, for docs and logs. */
64
+ endpoint: string;
65
+ inputSchema: I;
66
+ outputSchema: O;
67
+ annotations: ToolAnnotations;
68
+ handler: (api: AgentsRequester, args: z.output<I>) => Promise<z.output<O>>;
69
+ }
70
+ /** Task lifecycle (same values as the API). */
71
+ declare const AGENT_TASK_STATUSES: readonly ["open", "working", "input_required", "completed", "failed", "canceled", "rejected"];
72
+ /** The longest single long-poll the API holds (`/questions/:id/wait`). */
73
+ declare const QUESTION_WAIT_MAX_SECONDS = 25;
74
+ declare const AGENT_TOOLS: readonly AgentTool[];
75
+ declare const AGENT_TOOL_NAMES: string[];
76
+ declare function getAgentTool(name: string): AgentTool | undefined;
77
+ /**
78
+ * Validate `args` against the tool's input schema and run it. Throws
79
+ * `ToolError` for an unknown tool or invalid arguments, and for API
80
+ * failures (`toToolError` in `api.ts` words them for the model).
81
+ */
82
+ declare function runAgentTool(api: AgentsRequester, name: string, args: unknown): Promise<Json>;
83
+ /**
84
+ * The tools as JSON Schema function definitions, for LLM APIs that take
85
+ * `{ name, description, parameters }` (OpenAI / OpenRouter style).
86
+ */
87
+ declare function agentToolsAsJsonSchema(filter?: (tool: AgentTool) => boolean): Array<{
88
+ name: string;
89
+ description: string;
90
+ parameters: Json;
91
+ }>;
92
+
93
+ export { AGENT_TASK_STATUSES, AGENT_TOOLS, AGENT_TOOL_NAMES, QUESTION_WAIT_MAX_SECONDS, agentToolsAsJsonSchema, getAgentTool, runAgentTool };
94
+ export type { AgentScope, AgentTool, AgentsRequester };
@@ -0,0 +1,94 @@
1
+ import * as z from 'zod/v4';
2
+ import { ToolAnnotations, HttpMethod } from './inbox.js';
3
+ import '@channlize/core';
4
+
5
+ /**
6
+ * The Channlize for Agents toolset: names, zod input/output schemas and
7
+ * handlers for talking to other agents and to people in agent channels,
8
+ * through `/v1/agents`.
9
+ *
10
+ * Like `inbox.ts`, it has no dependency on the MCP server or its
11
+ * transports, so any agent can import it (`@channlize/mcp/agents`): the
12
+ * stdio/HTTP server registers these tools (`registerAgentTools` in
13
+ * `tools.ts`), and an LLM loop can run the same handlers with its own client
14
+ * (anything with a `request(method, path, { query, body })`).
15
+ *
16
+ * Identity: the key decides who the tools act as. An agent key acts as its
17
+ * agent; a multi-agent key acts as one of its agents, picked by `agentId`
18
+ * on the call or the requester's default `agentId` (`CHANNLIZE_AGENT_ID` /
19
+ * `--agent-id` on the server).
20
+ *
21
+ * Scopes: `agents:send` (post messages, create, update and hand off
22
+ * tasks, answer escalated questions), `agents:read` (directory, channels,
23
+ * inbox and ack, tasks, the org chart, search, and the graph, cost and
24
+ * metrics insights), `agents:ask_human` (ask people and read the answer)
25
+ * and `agents:admin` (`get_effective_policy` only).
26
+ *
27
+ * No admin-write tools: changing policies, teams, the org chart or an
28
+ * agent's status stays in the dashboard or `@channlize/core` with an
29
+ * `agents:admin` key. Policies are the guardrails on what an agent may do,
30
+ * so the agent that runs these tools shouldn't be able to rewrite them.
31
+ *
32
+ * No export or telemetry tools either (Phase 4): exports dump the org's
33
+ * whole history (messages, tasks, questions, the audit log), and the
34
+ * telemetry settings choose where every agent's traces go, with
35
+ * credentials. Both need an unbound `agents:admin` key and belong to the
36
+ * people who run Channlize: the dashboard, or `@channlize/core`
37
+ * (`agents.exports`, `agents.telemetry`). An agent reads its own history
38
+ * with `search_agent_messages` and its numbers with the insights tools,
39
+ * which only see what its key may read.
40
+ */
41
+
42
+ type Json = Record<string, unknown>;
43
+ type Query = Record<string, string | number | boolean | undefined>;
44
+ /** What a handler needs: `ChannlizeApi` from this package, or your own. */
45
+ interface AgentsRequester {
46
+ request<T>(method: HttpMethod, path: string,
47
+ /** `timeout` (ms) overrides the default, for the `get_answer` long-poll. */
48
+ options?: {
49
+ query?: Query;
50
+ body?: unknown;
51
+ timeout?: number;
52
+ }): Promise<T>;
53
+ /** The agent to act as when a call doesn't pass `agentId`. */
54
+ readonly agentId?: string;
55
+ }
56
+ /** The API key scope a tool needs. */
57
+ type AgentScope = 'agents:send' | 'agents:read' | 'agents:ask_human' | 'agents:admin';
58
+ interface AgentTool<I extends z.ZodObject = z.ZodObject, O extends z.ZodObject = z.ZodObject> {
59
+ name: string;
60
+ title: string;
61
+ description: string;
62
+ scope: AgentScope;
63
+ /** The HTTP endpoint it calls, for docs and logs. */
64
+ endpoint: string;
65
+ inputSchema: I;
66
+ outputSchema: O;
67
+ annotations: ToolAnnotations;
68
+ handler: (api: AgentsRequester, args: z.output<I>) => Promise<z.output<O>>;
69
+ }
70
+ /** Task lifecycle (same values as the API). */
71
+ declare const AGENT_TASK_STATUSES: readonly ["open", "working", "input_required", "completed", "failed", "canceled", "rejected"];
72
+ /** The longest single long-poll the API holds (`/questions/:id/wait`). */
73
+ declare const QUESTION_WAIT_MAX_SECONDS = 25;
74
+ declare const AGENT_TOOLS: readonly AgentTool[];
75
+ declare const AGENT_TOOL_NAMES: string[];
76
+ declare function getAgentTool(name: string): AgentTool | undefined;
77
+ /**
78
+ * Validate `args` against the tool's input schema and run it. Throws
79
+ * `ToolError` for an unknown tool or invalid arguments, and for API
80
+ * failures (`toToolError` in `api.ts` words them for the model).
81
+ */
82
+ declare function runAgentTool(api: AgentsRequester, name: string, args: unknown): Promise<Json>;
83
+ /**
84
+ * The tools as JSON Schema function definitions, for LLM APIs that take
85
+ * `{ name, description, parameters }` (OpenAI / OpenRouter style).
86
+ */
87
+ declare function agentToolsAsJsonSchema(filter?: (tool: AgentTool) => boolean): Array<{
88
+ name: string;
89
+ description: string;
90
+ parameters: Json;
91
+ }>;
92
+
93
+ export { AGENT_TASK_STATUSES, AGENT_TOOLS, AGENT_TOOL_NAMES, QUESTION_WAIT_MAX_SECONDS, agentToolsAsJsonSchema, getAgentTool, runAgentTool };
94
+ export type { AgentScope, AgentTool, AgentsRequester };