@channlize/mcp 0.1.1 → 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/dist/server.js CHANGED
@@ -1,1851 +1,1348 @@
1
1
  #!/usr/bin/env node
2
- #!/usr/bin/env node
3
2
  "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/server.ts
27
+ var import_node_util = require("util");
4
28
 
5
29
  // src/index.ts
6
- var import_server = require("@modelcontextprotocol/sdk/server/index.js");
7
- var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
8
- var import_types = require("@modelcontextprotocol/sdk/types.js");
30
+ var import_server2 = require("@modelcontextprotocol/server");
31
+ var import_stdio = require("@modelcontextprotocol/server/stdio");
9
32
 
10
- // src/resources/index.ts
11
- var CONTACT_FORM_SCHEMA = `{
12
- "name": "ContactFormData",
13
- "description": "Data structure for submitting a contact form inquiry",
14
- "type": "object",
15
- "properties": {
16
- "name": {
17
- "type": "string",
18
- "description": "Contact's full name",
19
- "required": true
20
- },
21
- "email": {
22
- "type": "string",
23
- "format": "email",
24
- "description": "Contact's email address",
25
- "required": true
26
- },
27
- "subject": {
28
- "type": "string",
29
- "description": "Subject line for the inquiry"
30
- },
31
- "message": {
32
- "type": "string",
33
- "description": "Message content",
34
- "required": true
35
- },
36
- "customFields": {
37
- "type": "object",
38
- "description": "Additional custom fields as key-value pairs",
39
- "additionalProperties": true
40
- },
41
- "pageUrl": {
42
- "type": "string",
43
- "description": "URL of the page where form was submitted"
44
- },
45
- "userAgent": {
46
- "type": "string",
47
- "description": "User agent string of the submitter"
33
+ // src/api.ts
34
+ var import_core = require("@channlize/core");
35
+ var ToolError = class extends Error {
36
+ constructor(message, statusCode, code) {
37
+ super(message);
38
+ this.statusCode = statusCode;
39
+ this.code = code;
40
+ this.name = "ToolError";
41
+ }
42
+ statusCode;
43
+ code;
44
+ };
45
+ function hintFor(status, apiMessage) {
46
+ switch (status) {
47
+ case 401:
48
+ return `${apiMessage}. The server API key is invalid, revoked or expired: create a new one under SDK & Integration \u2192 API keys and update CHANNLIZE_SERVER_API_KEY.`;
49
+ case 403:
50
+ return /permission/i.test(apiMessage) ? `${apiMessage}. This needs a server API key with the ${/write|notify/.test(apiMessage) ? "write" : "read"} scope: create one under SDK & Integration \u2192 API keys.` : apiMessage;
51
+ case 404:
52
+ return `${apiMessage}. Check the ID (and CHANNLIZE_PROJECT_ID): it must belong to the key's organization.`;
53
+ case 429:
54
+ return `${apiMessage}. Rate limited: wait a little before retrying.`;
55
+ default:
56
+ return apiMessage;
57
+ }
58
+ }
59
+ function toToolError(err) {
60
+ if (err instanceof ToolError) return err;
61
+ if (err instanceof import_core.ChannlizeError) {
62
+ return new ToolError(
63
+ err.statusCode ? hintFor(err.statusCode, err.message) : err.message,
64
+ err.statusCode,
65
+ err.code
66
+ );
67
+ }
68
+ return new ToolError(err instanceof Error ? err.message : String(err));
69
+ }
70
+ var ChannlizeApi = class {
71
+ constructor(config) {
72
+ this.config = config;
73
+ }
74
+ config;
75
+ projectIdPromise;
76
+ widgetClient;
77
+ get hasServerKey() {
78
+ return !!this.config.serverApiKey;
79
+ }
80
+ get hasWidgetKey() {
81
+ return !!this.config.widgetKey;
82
+ }
83
+ requireServerKey() {
84
+ if (!this.config.serverApiKey) {
85
+ throw new ToolError(
86
+ "This tool needs a server API key (ch_live_\u2026): set CHANNLIZE_SERVER_API_KEY or pass --server-api-key."
87
+ );
48
88
  }
89
+ return this.config.serverApiKey;
49
90
  }
50
- }`;
51
- var CHAT_SESSION_SCHEMA = `{
52
- "name": "ChatSession",
53
- "description": "Real-time chat session structure",
54
- "type": "object",
55
- "properties": {
56
- "id": {
57
- "type": "string",
58
- "description": "Unique session identifier"
59
- },
60
- "status": {
61
- "type": "string",
62
- "enum": ["active", "closed", "waiting"],
63
- "description": "Current session status"
64
- },
65
- "messages": {
66
- "type": "array",
67
- "items": {
68
- "$ref": "#/definitions/ChatMessage"
69
- },
70
- "description": "Array of messages in this session"
71
- },
72
- "metadata": {
73
- "type": "object",
74
- "description": "Custom metadata for the session"
75
- },
76
- "createdAt": {
77
- "type": "string",
78
- "format": "date-time",
79
- "description": "Session creation timestamp"
80
- },
81
- "updatedAt": {
82
- "type": "string",
83
- "format": "date-time",
84
- "description": "Last update timestamp"
91
+ async request(method, path, options = {}) {
92
+ const key = this.requireServerKey();
93
+ const url2 = new URL(`${this.config.baseUrl}${path}`);
94
+ for (const [k, v] of Object.entries(options.query ?? {})) {
95
+ if (v !== void 0 && v !== "") url2.searchParams.set(k, String(v));
85
96
  }
86
- },
87
- "definitions": {
88
- "ChatMessage": {
89
- "type": "object",
90
- "properties": {
91
- "id": { "type": "string" },
92
- "content": { "type": "string" },
93
- "timestamp": { "type": "string", "format": "date-time" },
94
- "author": { "type": "string", "enum": ["user", "agent"] },
95
- "authorData": {
96
- "type": "object",
97
- "properties": {
98
- "name": { "type": "string" },
99
- "email": { "type": "string" },
100
- "avatarUrl": { "type": "string" }
101
- }
97
+ let res;
98
+ try {
99
+ res = await fetch(url2, {
100
+ method,
101
+ headers: {
102
+ Authorization: `Bearer ${key}`,
103
+ Accept: "application/json",
104
+ ...options.body !== void 0 ? { "Content-Type": "application/json" } : {},
105
+ ...options.headers
102
106
  },
103
- "attachments": {
104
- "type": "array",
105
- "items": { "$ref": "#/definitions/FileAttachment" }
106
- }
107
+ body: options.body !== void 0 ? JSON.stringify(options.body) : void 0,
108
+ signal: AbortSignal.timeout(this.config.timeout)
109
+ });
110
+ } catch (err) {
111
+ const e = err;
112
+ if (e.name === "TimeoutError" || e.name === "AbortError") {
113
+ throw new ToolError(
114
+ `Channlize API timed out after ${this.config.timeout} ms (${method} ${url2.pathname}). Retry, or raise CHANNLIZE_TIMEOUT.`,
115
+ 408,
116
+ "TIMEOUT"
117
+ );
107
118
  }
108
- },
109
- "FileAttachment": {
110
- "type": "object",
111
- "properties": {
112
- "id": { "type": "string" },
113
- "name": { "type": "string" },
114
- "size": { "type": "number" },
115
- "type": { "type": "string" },
116
- "url": { "type": "string" }
119
+ throw new ToolError(
120
+ `Could not reach the Channlize API at ${this.config.baseUrl}: ${e.message}`
121
+ );
122
+ }
123
+ const text = await res.text();
124
+ let data = void 0;
125
+ if (text) {
126
+ try {
127
+ data = JSON.parse(text);
128
+ } catch {
129
+ data = void 0;
117
130
  }
118
131
  }
132
+ if (!res.ok) {
133
+ const body = data ?? {};
134
+ const raw = Array.isArray(body.message) ? body.message.join(", ") : body.message;
135
+ const apiMessage = raw || (typeof body.error === "string" && /\s/.test(body.error) ? body.error : "") || `HTTP ${res.status} ${res.statusText}`.trim();
136
+ throw new ToolError(
137
+ hintFor(res.status, apiMessage.replace(/\.$/, "")),
138
+ res.status,
139
+ body.code ?? (typeof body.error === "string" ? body.error : void 0)
140
+ );
141
+ }
142
+ if (data === void 0) {
143
+ throw new ToolError(
144
+ `Unexpected non-JSON response from ${method} ${url2.pathname}`
145
+ );
146
+ }
147
+ return data;
119
148
  }
120
- }`;
121
- var API_REFERENCE = `# Channlize API Reference
122
-
123
- ## Overview
124
- Channlize is a developer-first support platform that routes customer conversations directly into Slack.
125
-
126
- Base URL: \`http://api.channlize.com\` (default) or your configured API endpoint
127
-
128
- ## Authentication
129
- All API requests require a Widget Key (API key) in the request body or as a header.
130
-
131
- ## Endpoints
132
-
133
- ### Contact Forms
134
-
135
- **POST /public/contact**
136
- Submit a contact form inquiry.
149
+ /**
150
+ * `CHANNLIZE_PROJECT_ID`, or the first project of the key's organization
151
+ * (`GET /projects/me`), looked up once.
152
+ */
153
+ projectId() {
154
+ if (this.config.projectId) return Promise.resolve(this.config.projectId);
155
+ this.projectIdPromise ??= this.request(
156
+ "GET",
157
+ "/projects/me"
158
+ ).then((r) => {
159
+ if (!r.project?.id) {
160
+ throw new ToolError(
161
+ "Could not find a project for this key: set CHANNLIZE_PROJECT_ID."
162
+ );
163
+ }
164
+ return r.project.id;
165
+ }).catch((err) => {
166
+ this.projectIdPromise = void 0;
167
+ throw err;
168
+ });
169
+ return this.projectIdPromise;
170
+ }
171
+ /** `@channlize/core` client bound to the widget key (visitor tools). */
172
+ widget() {
173
+ if (!this.config.widgetKey) {
174
+ throw new ToolError(
175
+ "This tool needs the public widget key (wk_\u2026): set CHANNLIZE_WIDGET_KEY or pass --widget-key."
176
+ );
177
+ }
178
+ this.widgetClient ??= new import_core.ChannlizeClient({
179
+ apiKey: this.config.widgetKey,
180
+ projectId: this.config.projectId ?? "",
181
+ baseUrl: this.config.baseUrl,
182
+ timeout: this.config.timeout
183
+ });
184
+ return this.widgetClient;
185
+ }
186
+ /** `@channlize/core` client for `notify` (server key + project). */
187
+ async notifier() {
188
+ const serverApiKey = this.requireServerKey();
189
+ return new import_core.ChannlizeClient({
190
+ // core requires a widget key; notify only uses serverApiKey.
191
+ apiKey: this.config.widgetKey ?? "unused",
192
+ serverApiKey,
193
+ projectId: await this.projectId(),
194
+ baseUrl: this.config.baseUrl,
195
+ timeout: this.config.timeout
196
+ });
197
+ }
198
+ };
137
199
 
138
- Request Body:
139
- \`\`\`json
140
- {
141
- "widgetKey": "your-widget-key",
142
- "name": "John Doe",
143
- "email": "john@example.com",
144
- "subject": "Support Request",
145
- "message": "I need help with...",
146
- "pageUrl": "https://example.com/contact",
147
- "userAgent": "Mozilla/5.0..."
200
+ // src/config.ts
201
+ var DEFAULT_BASE_URL = "https://api.channlize.com";
202
+ var DEFAULT_TIMEOUT = 1e4;
203
+ var ConfigError = class extends Error {
204
+ constructor(message) {
205
+ super(message);
206
+ this.name = "ConfigError";
207
+ }
208
+ };
209
+ function keyKind(key) {
210
+ return key.startsWith("wk_") ? "widget" : "server";
148
211
  }
149
- \`\`\`
150
-
151
- Response:
152
- \`\`\`json
153
- {
154
- "success": true,
155
- "conversationId": "3f0c\u2026",
156
- "message": "Thank you for contacting us! We will get back to you soon."
212
+ function clean(value) {
213
+ const v = value?.trim();
214
+ return v ? v : void 0;
157
215
  }
158
- \`\`\`
159
-
160
- ### Chat Sessions
161
-
162
- **POST /v1/chat/sessions**
163
- Create a new chat session.
164
-
165
- Request Body:
166
- \`\`\`json
167
- {
168
- "metadata": {
169
- "pageUrl": "https://example.com",
170
- "userAgent": "Mozilla/5.0...",
171
- "customField": "value"
216
+ function resolveConfig(config) {
217
+ let serverApiKey = clean(config.serverApiKey);
218
+ let widgetKey = clean(config.widgetKey);
219
+ const apiKey = clean(config.apiKey);
220
+ if (serverApiKey && keyKind(serverApiKey) === "widget") {
221
+ throw new ConfigError(
222
+ "CHANNLIZE_SERVER_API_KEY / --server-api-key holds a widget key (wk_\u2026). Widget keys are public and only work for the visitor tools: pass it as CHANNLIZE_WIDGET_KEY / --widget-key, and create a server API key (ch_live_\u2026) under SDK & Integration \u2192 API keys in the Channlize dashboard."
223
+ );
172
224
  }
173
- }
174
- \`\`\`
175
-
176
- Response:
177
- \`\`\`json
178
- {
179
- "data": {
180
- "session": {
181
- "id": "session_123",
182
- "status": "active",
183
- "messages": [],
184
- "createdAt": "2025-01-15T10:00:00Z"
185
- },
186
- "sseEndpoint": "/v1/chat/sessions/session_123/events"
225
+ if (widgetKey && keyKind(widgetKey) === "server") {
226
+ throw new ConfigError(
227
+ "CHANNLIZE_WIDGET_KEY / --widget-key holds a server API key. Pass server keys (ch_live_\u2026) as CHANNLIZE_SERVER_API_KEY / --server-api-key; widget keys start with wk_."
228
+ );
229
+ }
230
+ if (apiKey) {
231
+ if (keyKind(apiKey) === "widget") widgetKey ??= apiKey;
232
+ else serverApiKey ??= apiKey;
233
+ }
234
+ if (!serverApiKey && !widgetKey) {
235
+ throw new ConfigError(
236
+ "A Channlize key is required. Set CHANNLIZE_SERVER_API_KEY (or --server-api-key) to a server API key (ch_live_\u2026) from SDK & Integration \u2192 API keys in the Channlize dashboard. Use a read-scoped key for read-only access, or add the write scope to send events, logs and conversations."
237
+ );
238
+ }
239
+ const timeout = config.timeout ?? DEFAULT_TIMEOUT;
240
+ if (!Number.isFinite(timeout) || timeout <= 0) {
241
+ throw new ConfigError(
242
+ `Invalid timeout "${config.timeout}": use milliseconds, e.g. 10000`
243
+ );
187
244
  }
245
+ const baseUrl = (clean(config.baseUrl) ?? DEFAULT_BASE_URL).replace(
246
+ /\/+$/,
247
+ ""
248
+ );
249
+ try {
250
+ new URL(baseUrl);
251
+ } catch {
252
+ throw new ConfigError(
253
+ `Invalid base URL "${config.baseUrl}": use e.g. ${DEFAULT_BASE_URL}`
254
+ );
255
+ }
256
+ return {
257
+ serverApiKey,
258
+ widgetKey,
259
+ projectId: clean(config.projectId),
260
+ baseUrl,
261
+ timeout
262
+ };
188
263
  }
189
- \`\`\`
264
+ function configFromEnv(env = process.env) {
265
+ const timeout = clean(env.CHANNLIZE_TIMEOUT);
266
+ return {
267
+ serverApiKey: clean(env.CHANNLIZE_SERVER_API_KEY),
268
+ widgetKey: clean(env.CHANNLIZE_WIDGET_KEY),
269
+ apiKey: clean(env.CHANNLIZE_API_KEY),
270
+ projectId: clean(env.CHANNLIZE_PROJECT_ID),
271
+ baseUrl: clean(env.CHANNLIZE_BASE_URL),
272
+ timeout: timeout ? Number(timeout) : void 0
273
+ };
274
+ }
275
+
276
+ // src/prompts.ts
277
+ var z = __toESM(require("zod/v4"));
190
278
 
191
- **POST /v1/chat/sessions/:sessionId/messages**
192
- Send a message in a chat session.
279
+ // src/docs.ts
280
+ var QUICK_START = `# Channlize quick start
193
281
 
194
- Request: FormData with 'content' field and optional file attachments
282
+ Channlize routes website conversations and app events to Slack.
195
283
 
196
- **GET /v1/chat/sessions/:sessionId/messages**
197
- Get messages for a session. Query params: limit, offset
284
+ ## Keys
285
+ - **Widget key** (\`wk_\u2026\`): public, safe in browser code. Used by the chat widget,
286
+ contact form and browser log tracking (\`apiKey\` in the SDK config).
287
+ - **Server API key** (\`ch_live_\u2026\`): secret, server only (\`serverApiKey\`). Scopes:
288
+ \`read\` (conversations, contacts, logs, Events) and \`write\` (notify, server logs,
289
+ create conversations/contacts). Create both under SDK & Integration \u2192 API keys.
290
+ - **Project ID**: shown next to each widget key.
291
+
292
+ ## Install
293
+ \`\`\`bash
294
+ npm install @channlize/core # any JS runtime
295
+ npm install @channlize/react # React components and hooks
296
+ \`\`\`
198
297
 
199
- **POST /v1/chat/sessions/:sessionId/close**
200
- Close a chat session.
298
+ ## Browser
299
+ \`\`\`ts
300
+ import { ChannlizeClient } from '@channlize/core';
201
301
 
202
- ## Error Responses
302
+ const client = new ChannlizeClient({ apiKey: 'wk_\u2026', projectId: '<project-id>' });
303
+ await client.submitContactForm({ name: 'Ada', email: 'ada@example.com', message: 'Hi!' });
304
+ await client.trackEvent('checkout.started', { properties: { cartId: 'c_1' } });
305
+ \`\`\`
203
306
 
204
- Status codes:
205
- - 400: Validation error
206
- - 401: Authentication failed
207
- - 429: Rate limit exceeded
208
- - 500: Server error
307
+ ## Server
308
+ \`\`\`ts
309
+ import { createNotifier } from '@channlize/core';
209
310
 
210
- Error format:
211
- \`\`\`json
212
- {
213
- "error": "Error message",
214
- "code": "ERROR_CODE",
215
- "field": "fieldName" // for validation errors
216
- }
311
+ // Reads CHANNLIZE_SERVER_API_KEY (or CHANNLIZE_API_KEY) and CHANNLIZE_PROJECT_ID.
312
+ const notify = createNotifier();
313
+ await notify('user.subscribed', { username: 'ada', plan: 'Pro' });
217
314
  \`\`\`
218
- `;
219
- var QUICK_START = `# Channlize Quick Start
220
315
 
221
- ## Installation
316
+ Errors are \`ChannlizeError\` (\`statusCode\`, \`code\`), with subclasses
317
+ \`ValidationError\` (400), \`AuthenticationError\` (401) and \`RateLimitError\` (429).
318
+ `;
319
+ var REACT_COMPONENTS = `# @channlize/react
222
320
 
223
321
  \`\`\`bash
224
- # For React applications
225
322
  npm install @channlize/react
226
-
227
- # For non-React applications
228
- npm install @channlize/core
229
323
  \`\`\`
230
324
 
231
- ## React Setup
232
-
233
- 1. Wrap your app with ChannlizeProvider:
325
+ ## Floating chat widget (all-in-one)
326
+ \`ChannlizeChat\` includes the provider, launcher button and chat window.
234
327
 
235
328
  \`\`\`tsx
236
- import { ChannlizeProvider } from '@channlize/react';
329
+ 'use client';
330
+ import { ChannlizeChat } from '@channlize/react';
237
331
 
238
- function App() {
332
+ export function SupportWidget() {
239
333
  return (
240
- <ChannlizeProvider
334
+ <ChannlizeChat
241
335
  config={{
242
- apiKey: 'your-widget-key',
243
- projectId: 'your-project-id',
336
+ apiKey: process.env.NEXT_PUBLIC_CHANNLIZE_WIDGET_KEY!, // wk_\u2026, public
337
+ projectId: process.env.NEXT_PUBLIC_CHANNLIZE_PROJECT_ID!,
244
338
  }}
245
- >
246
- <YourApp />
247
- </ChannlizeProvider>
248
- );
249
- }
250
- \`\`\`
251
-
252
- 2. Use the Form component:
253
-
254
- \`\`\`tsx
255
- import { Form } from '@channlize/react';
256
-
257
- function ContactPage() {
258
- return (
259
- <Form
260
- onSuccess={() => console.log('Form submitted!')}
261
- submitText="Send Message"
339
+ position="bottom-right"
340
+ primaryColor="#2563eb"
341
+ theme="auto"
342
+ chatProps={{ title: 'Acme Support', greeting: 'Hi there!', welcomeMessage: 'Ask us anything.' }}
262
343
  />
263
344
  );
264
345
  }
265
346
  \`\`\`
266
347
 
267
- 3. Add live chat:
348
+ Key props: \`position\` ('bottom-right' | 'bottom-left' | 'top-right' | 'top-left'),
349
+ \`primaryColor\`, \`theme\` ('auto' | 'light' | 'dark'), \`defaultOpen\`, \`showOnMobile\`,
350
+ \`offset\`, \`zIndex\`, \`onOpen\`, \`onClose\`, and \`chatProps\` (SupportChat props).
268
351
 
352
+ ## Provider + inline components
269
353
  \`\`\`tsx
270
- import { Chat } from '@channlize/react';
354
+ import { ChannlizeProvider, SupportChat, ContactForm } from '@channlize/react';
271
355
 
272
- function SupportPage() {
273
- return (
274
- <Chat
275
- autoStart={true}
276
- title="Customer Support"
277
- placeholder="How can we help?"
278
- />
279
- );
280
- }
356
+ <ChannlizeProvider config={{ apiKey: 'wk_\u2026', projectId: '<project-id>' }}>
357
+ <SupportChat title="Need help?" greeting="Hi!" suggestions={['Pricing', 'Billing']} />
358
+ <ContactForm submitText="Send" successMessage="Thanks! We'll reply by email." />
359
+ </ChannlizeProvider>
281
360
  \`\`\`
282
361
 
283
- ## Core SDK Usage
362
+ SupportChat props include \`title\`, \`greeting\`, \`welcomeMessage\`, \`suggestions\`,
363
+ \`placeholder\`, \`allowAttachments\`, \`allowEndChat\`, \`theme\`, \`primaryColor\`,
364
+ \`locale\`, \`maxLength\`, \`labels\` and the \`useChatSession\` options
365
+ (\`autoConnect\`, \`metadata\`, \`persistSession\`, \`onMessage\`, \`onError\`).
366
+
367
+ ContactForm props: \`customFields\`, \`onSuccess\`, \`onError\`, \`className\`, \`styles\`,
368
+ \`submitText\`, \`successMessage\`.
369
+
370
+ ## Hooks (inside ChannlizeProvider)
371
+ - \`useChatSession(options)\` \u2192 \`{ session, messages, isConnecting, isConnected, error,
372
+ sendMessage, startSession, endSession, reconnect }\`
373
+ - \`useContactForm({ onSuccess, onError })\` \u2192 \`{ submit, isSubmitting, isSuccess, error, reset }\`
374
+ - \`useChannlizeAnalytics()\` \u2192 \`{ track(name, { properties, severity }) }\` (log tracking)
375
+ - \`useChannlize()\` \u2192 \`{ client, config }\`
376
+
377
+ ## Next.js App Router
378
+ Components use state and effects: render them from a \`'use client'\` file. The widget
379
+ key is public (\`NEXT_PUBLIC_\u2026\` is fine); never put the server API key in a
380
+ \`NEXT_PUBLIC_\` variable.
381
+ `;
382
+ var EVENTS = `# Events: notify Slack from code
284
383
 
285
- For non-React applications:
384
+ Each event key (e.g. \`user.subscribed\`) has a Slack channel and a message template
385
+ with \`{{field}}\` placeholders, set under Events in the dashboard. A key that isn't set
386
+ up yet is saved as a disabled draft the first time it is sent (\`status: 'skipped'\`),
387
+ so you can ship code first and configure it after. Server only: needs a server API key
388
+ with the \`write\` scope.
286
389
 
287
- \`\`\`typescript
288
- import { ChannlizeClient } from '@channlize/core';
390
+ \`\`\`ts
391
+ import { createNotifier } from '@channlize/core';
289
392
 
290
- const client = new ChannlizeClient({
291
- apiKey: 'your-widget-key',
292
- projectId: 'your-project-id',
293
- });
393
+ type AppEvents = {
394
+ 'user.subscribed': { username: string; plan: string };
395
+ 'invoice.paid': { amount: string; customer: string };
396
+ };
294
397
 
295
- // Submit contact form
296
- await client.submitContactForm({
297
- name: 'John Doe',
298
- email: 'john@example.com',
299
- message: 'I need help!',
300
- });
398
+ // Env: CHANNLIZE_SERVER_API_KEY (or CHANNLIZE_API_KEY), CHANNLIZE_PROJECT_ID.
399
+ const notify = createNotifier<AppEvents>();
301
400
 
302
- // Create chat session
303
- const { session } = await client.createChatSession();
401
+ const res = await notify('user.subscribed', { username: 'ada', plan: 'Pro' });
402
+ // res.status: 'queued' | 'skipped' (see res.reason); res.duplicate on a repeated key
304
403
 
305
- // Send message
306
- await client.sendMessage(session.id, 'Hello!');
404
+ // Retry-safe: an idempotency key already used posts nothing.
405
+ await notify('invoice.paid', { amount: '$49', customer: 'ada@example.com' }, { idempotencyKey: 'inv_123' });
307
406
  \`\`\`
308
407
 
309
- ## Configuration
310
-
311
- - \`apiKey\`: Your Channlize widget key (required)
312
- - \`projectId\`: Your project ID (required)
313
- - \`baseUrl\`: API base URL (optional, defaults to api.channlize.com)
314
- - \`timeout\`: Request timeout in ms (optional, defaults to 10000)
315
- `;
316
- var REACT_COMPONENTS = `# Channlize React Components
317
-
318
- ## ChannlizeProvider
319
-
320
- Wrap your app to provide Channlize configuration to all components.
408
+ To type keys app-wide instead, augment the interface:
409
+ \`\`\`ts
410
+ declare module '@channlize/core' {
411
+ interface ChannlizeEvents {
412
+ 'user.subscribed': { username: string; plan: string };
413
+ }
414
+ }
415
+ \`\`\`
321
416
 
322
- \`\`\`tsx
323
- <ChannlizeProvider config={{ apiKey, projectId }}>
324
- {children}
325
- </ChannlizeProvider>
417
+ Over HTTP:
418
+ \`\`\`bash
419
+ curl -X POST https://api.channlize.com/v1/notify \\
420
+ -H "Authorization: Bearer $CHANNLIZE_SERVER_API_KEY" \\
421
+ -H "Content-Type: application/json" \\
422
+ -H "Idempotency-Key: signup-42" \\
423
+ -d '{"projectId":"<project-id>","event":"user.subscribed","data":{"username":"ada"}}'
326
424
  \`\`\`
327
425
 
328
- ## Form
426
+ Log tracking is separate: \`client.trackEvent\` (browser) / \`trackServerEvent\` (server)
427
+ record log lines shown under Log tracking.
428
+ `;
329
429
 
330
- A complete contact form with validation and submission handling.
430
+ // src/prompts.ts
431
+ function user(text) {
432
+ return {
433
+ messages: [
434
+ { role: "user", content: { type: "text", text } }
435
+ ]
436
+ };
437
+ }
438
+ function registerServerPrompts(server) {
439
+ server.registerPrompt(
440
+ "triage-conversations",
441
+ {
442
+ title: "Triage open conversations",
443
+ description: "Review open support conversations, rank them by urgency and draft a reply for each.",
444
+ argsSchema: z.object({
445
+ status: z.string().optional().describe(
446
+ "Status to triage: open (default), pending, resolved or closed"
447
+ ),
448
+ limit: z.string().optional().describe("How many conversations (default 10)")
449
+ })
450
+ },
451
+ ({ status, limit }) => user(`Triage my Channlize support inbox.
331
452
 
332
- ### Props
333
- - \`customFields?: Array<CustomField>\` - Additional form fields
334
- - \`onSuccess?: () => void\` - Success callback
335
- - \`onError?: (error: Error) => void\` - Error callback
336
- - \`className?: string\` - CSS class name
337
- - \`styles?: FormStyles\` - Custom styles object
338
- - \`submitText?: string\` - Submit button text
339
- - \`successMessage?: string\` - Success message text
453
+ 1. Call list_conversations with status "${status || "open"}" and limit ${Number(limit) || 10}.
454
+ 2. For each conversation, call get_conversation to read the thread.
455
+ 3. Return a table sorted by urgency: subject, customer, how long it has waited, current priority, a suggested priority (low / normal / high / urgent) and a one-line reason.
456
+ 4. Under the table, draft a short, friendly reply for each conversation that needs one, and flag anything that looks like a bug, an outage or a billing problem.
340
457
 
341
- ### Example
342
- \`\`\`tsx
343
- <Form
344
- customFields={[
458
+ Replies and status or priority changes are made by the team in the Slack thread or the Channlize dashboard, so present the drafts for me to send; don't invent facts about my product.`)
459
+ );
460
+ server.registerPrompt(
461
+ "summarize-conversation",
345
462
  {
346
- name: 'department',
347
- label: 'Department',
348
- type: 'select',
349
- options: ['Sales', 'Support'],
350
- required: true,
463
+ title: "Summarize a conversation",
464
+ description: "Summarize one conversation and suggest the next step.",
465
+ argsSchema: z.object({
466
+ conversationId: z.string().describe("Conversation ID")
467
+ })
351
468
  },
352
- ]}
353
- onSuccess={() => alert('Sent!')}
354
- styles={{
355
- button: { background: '#007bff', color: 'white' },
356
- }}
357
- />
358
- \`\`\`
469
+ ({ conversationId }) => user(
470
+ `Call get_conversation with conversationId "${conversationId}". Summarize it in five bullet points or fewer: what the customer wants, what has been answered, what is still open, and the customer's mood. Then suggest the next step and draft a reply if one is needed.`
471
+ )
472
+ );
473
+ server.registerPrompt(
474
+ "check-events",
475
+ {
476
+ title: "Check Events delivery",
477
+ description: "Check that Events reach Slack: list them and explain failed or skipped deliveries.",
478
+ argsSchema: z.object({})
479
+ },
480
+ () => user(`Check my Channlize Events setup.
359
481
 
360
- ## Chat
482
+ 1. Call list_event_definitions and list the Events, flagging disabled drafts and Events without a Slack channel.
483
+ 2. Call list_event_deliveries (limit 50) and group the deliveries by status.
484
+ 3. For each failed or skipped delivery, explain the reason and how to fix it (enable the Event, pick a channel under Events in the dashboard, invite the Slack app to the channel, or connect Slack).`)
485
+ );
486
+ }
487
+ function registerGuidePrompts(server) {
488
+ server.registerPrompt(
489
+ "setup-events",
490
+ {
491
+ title: "Add Events to my app",
492
+ description: "Add Channlize Events (notify Slack from server code) to the current codebase.",
493
+ argsSchema: z.object({
494
+ events: z.string().optional().describe('Events to add, e.g. "signup, subscription upgrade"')
495
+ })
496
+ },
497
+ ({ events }) => user(`Add Channlize Events to this codebase${events ? ` for: ${events}` : ""}.
361
498
 
362
- Real-time chat widget with message history and file attachments.
499
+ Find where these things happen on the server, then call notify there with a typed event map and an idempotency key derived from the record ID. Never call it from browser code. Read the server key from CHANNLIZE_SERVER_API_KEY and the project from CHANNLIZE_PROJECT_ID, and add both to the env example file. Don't let a notify failure break the request: catch and log it.
363
500
 
364
- ### Props
365
- - \`autoStart?: boolean\` - Auto-start chat session
366
- - \`metadata?: Record<string, unknown>\` - Session metadata
367
- - \`onMessage?: (message: ChatMessage) => void\` - New message callback
368
- - \`onError?: (error: Error) => void\` - Error callback
369
- - \`className?: string\` - CSS class name
370
- - \`styles?: ChatStyles\` - Custom styles object
371
- - \`title?: string\` - Chat header title
372
- - \`placeholder?: string\` - Input placeholder text
373
- - \`maxHeight?: string\` - Maximum container height
501
+ Reference:
374
502
 
375
- ### Example
376
- \`\`\`tsx
377
- <Chat
378
- autoStart={true}
379
- title="Need Help?"
380
- placeholder="Type your message..."
381
- onMessage={(msg) => console.log('New:', msg)}
382
- styles={{
383
- container: { width: '400px', height: '500px' },
384
- header: { background: '#007bff', color: 'white' },
385
- }}
386
- />
387
- \`\`\`
503
+ ${EVENTS}`)
504
+ );
505
+ server.registerPrompt(
506
+ "setup-live-chat",
507
+ {
508
+ title: "Add the chat widget",
509
+ description: "Add the Channlize live chat widget to a React or Next.js app.",
510
+ argsSchema: z.object({
511
+ framework: z.string().optional().describe("e.g. Next.js App Router, Vite, Remix")
512
+ })
513
+ },
514
+ ({ framework }) => user(`Add the Channlize chat widget to this ${framework || "React"} app: install @channlize/react, render ChannlizeChat once in the root layout (from a 'use client' component on Next.js), read the widget key and project ID from public env variables, and match primaryColor to the app's brand color.
388
515
 
389
- ## Hooks
516
+ Reference:
390
517
 
391
- ### useContactForm()
392
- Hook for managing contact form state.
518
+ ${REACT_COMPONENTS}`)
519
+ );
520
+ server.registerPrompt(
521
+ "integrate-contact-form",
522
+ {
523
+ title: "Add a contact form",
524
+ description: "Add a Channlize contact form page to a React app.",
525
+ argsSchema: z.object({
526
+ styling: z.string().optional().describe("Styling approach, e.g. Tailwind or CSS modules")
527
+ })
528
+ },
529
+ ({ styling }) => user(`Add a contact page to this app using Channlize. Use the ContactForm component (or useContactForm with a custom form${styling ? ` styled with ${styling}` : ""}) inside ChannlizeProvider, show a clear success state, and surface errors from onError.
393
530
 
394
- \`\`\`tsx
395
- const { submit, isSubmitting, isSuccess, error, reset } = useContactForm({
396
- onSuccess: (response) => console.log('Submitted:', response),
397
- onError: (error) => console.error('Failed:', error),
398
- });
399
- \`\`\`
531
+ Reference:
400
532
 
401
- ### useChatSession()
402
- Hook for managing chat sessions.
533
+ ${REACT_COMPONENTS}`)
534
+ );
535
+ }
403
536
 
404
- \`\`\`tsx
405
- const {
406
- session,
407
- messages,
408
- isConnecting,
409
- isConnected,
410
- error,
411
- sendMessage,
412
- startSession,
413
- endSession,
414
- } = useChatSession({
415
- autoConnect: false,
416
- metadata: { page: 'support' },
417
- onMessage: (message) => console.log('New message:', message),
537
+ // src/resources.ts
538
+ var markdown = (uri, text) => ({
539
+ contents: [{ uri, mimeType: "text/markdown", text }]
418
540
  });
419
- \`\`\`
420
-
421
- ## Styling
422
-
423
- All components accept \`styles\` prop for custom styling or \`className\` for CSS classes.
424
-
425
- CSS class names:
426
- - \`.channlize-form\`
427
- - \`.channlize-field\`
428
- - \`.channlize-error\`
429
- - \`.channlize-chat-container\`
430
- - \`.channlize-chat-header\`
431
- - \`.channlize-chat-messages\`
432
- - \`.channlize-message\`
433
- - \`.channlize-message-user\`
434
- - \`.channlize-message-agent\`
435
- `;
436
- var resources = [
437
- {
438
- uri: "channlize://schema/contact-form",
439
- name: "Contact Form Schema",
440
- description: "JSON schema for contact form data structure",
441
- mimeType: "application/json",
442
- getText: () => CONTACT_FORM_SCHEMA
443
- },
444
- {
445
- uri: "channlize://schema/chat-session",
446
- name: "Chat Session Schema",
447
- description: "JSON schema for chat session data structure",
448
- mimeType: "application/json",
449
- getText: () => CHAT_SESSION_SCHEMA
450
- },
451
- {
452
- uri: "channlize://docs/api-reference",
453
- name: "API Reference",
454
- description: "Complete Channlize API reference documentation",
455
- mimeType: "text/markdown",
456
- getText: () => API_REFERENCE
457
- },
458
- {
459
- uri: "channlize://docs/quick-start",
460
- name: "Quick Start Guide",
461
- description: "Quick start guide for using Channlize SDK",
462
- mimeType: "text/markdown",
463
- getText: () => QUICK_START
464
- },
465
- {
466
- uri: "channlize://docs/react-components",
467
- name: "React Components Documentation",
468
- description: "Documentation for Channlize React components and hooks",
469
- mimeType: "text/markdown",
470
- getText: () => REACT_COMPONENTS
541
+ function registerResources(server, api) {
542
+ server.registerResource(
543
+ "quick-start",
544
+ "channlize://docs/quick-start",
545
+ {
546
+ title: "Channlize quick start",
547
+ description: "Keys, install, and first calls from the browser and the server.",
548
+ mimeType: "text/markdown"
549
+ },
550
+ (uri) => markdown(uri.href, QUICK_START)
551
+ );
552
+ server.registerResource(
553
+ "react-components",
554
+ "channlize://docs/react-components",
555
+ {
556
+ title: "@channlize/react components and hooks",
557
+ description: "ChannlizeChat, SupportChat, ContactForm, hooks and Next.js notes.",
558
+ mimeType: "text/markdown"
559
+ },
560
+ (uri) => markdown(uri.href, REACT_COMPONENTS)
561
+ );
562
+ server.registerResource(
563
+ "events",
564
+ "channlize://docs/events",
565
+ {
566
+ title: "Events: notify Slack from code",
567
+ description: "createNotifier / notify with typed events, idempotency and curl.",
568
+ mimeType: "text/markdown"
569
+ },
570
+ (uri) => markdown(uri.href, EVENTS)
571
+ );
572
+ if (api.hasServerKey) {
573
+ server.registerResource(
574
+ "project",
575
+ "channlize://project",
576
+ {
577
+ title: "Current Channlize project",
578
+ description: "The key's project: ID, name, totals, permissions and Slack connection.",
579
+ mimeType: "application/json"
580
+ },
581
+ async (uri) => {
582
+ const r = await api.request(
583
+ "GET",
584
+ "/projects/me"
585
+ );
586
+ return {
587
+ contents: [
588
+ {
589
+ uri: uri.href,
590
+ mimeType: "application/json",
591
+ text: JSON.stringify(r.project, null, 2)
592
+ }
593
+ ]
594
+ };
595
+ }
596
+ );
471
597
  }
472
- ];
473
- function getResource(uri) {
474
- return resources.find((r) => r.uri === uri);
475
- }
476
- function listResources() {
477
- return resources;
478
598
  }
479
599
 
480
- // src/tools/index.ts
481
- var import_core = require("@channlize/core");
482
- function createClient(context) {
483
- return new import_core.ChannlizeClient({
484
- apiKey: context.config.apiKey,
485
- projectId: context.config.projectId || "",
486
- baseUrl: context.config.baseUrl,
487
- timeout: context.config.timeout
488
- });
600
+ // src/tools.ts
601
+ var z2 = __toESM(require("zod/v4"));
602
+ var STATUSES = ["open", "pending", "resolved", "closed"];
603
+ var PRIORITIES = ["low", "normal", "high", "urgent"];
604
+ var SEVERITIES = ["info", "warning", "error"];
605
+ var EVENT_KEY = /^[a-z0-9][a-z0-9._-]*$/;
606
+ function ok(data) {
607
+ return {
608
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
609
+ structuredContent: data
610
+ };
489
611
  }
490
- var tools = [
491
- {
492
- name: "submit_contact_form",
493
- description: "Submit a contact form inquiry to Channlize. This creates a new conversation thread in Slack where the team can respond.",
494
- inputSchema: {
495
- type: "object",
496
- properties: {
497
- name: {
498
- type: "string",
499
- description: "Contact's full name"
500
- },
501
- email: {
502
- type: "string",
503
- description: "Contact's email address"
504
- },
505
- subject: {
506
- type: "string",
507
- description: "Subject line for the inquiry (optional)"
508
- },
509
- message: {
510
- type: "string",
511
- description: "The message content"
512
- },
513
- customFields: {
514
- type: "object",
515
- description: "Additional custom fields as key-value pairs (optional)",
516
- additionalProperties: true
517
- },
518
- pageUrl: {
519
- type: "string",
520
- description: "URL of the page where form was submitted (optional)"
521
- },
522
- userAgent: {
523
- type: "string",
524
- description: "User agent string of the submitter (optional)"
525
- }
526
- },
527
- required: ["name", "email", "message"]
612
+ async function run(fn) {
613
+ try {
614
+ return ok(await fn());
615
+ } catch (err) {
616
+ const e = toToolError(err);
617
+ return {
618
+ content: [{ type: "text", text: e.message }],
619
+ isError: true
620
+ };
621
+ }
622
+ }
623
+ var record2 = z2.record(z2.string(), z2.unknown());
624
+ var loose = z2.looseObject({});
625
+ var page = {
626
+ total: z2.number(),
627
+ limit: z2.number(),
628
+ offset: z2.number(),
629
+ hasMore: z2.boolean()
630
+ };
631
+ var conversationSummary = z2.looseObject({
632
+ id: z2.string(),
633
+ subject: z2.string().nullable().optional(),
634
+ status: z2.string(),
635
+ priority: z2.string().nullable().optional(),
636
+ customerEmail: z2.string().nullable().optional(),
637
+ customerName: z2.string().nullable().optional(),
638
+ tags: z2.array(z2.string()).nullable().optional(),
639
+ createdAt: z2.string().optional()
640
+ });
641
+ var READ = {
642
+ readOnlyHint: true,
643
+ destructiveHint: false,
644
+ idempotentHint: true,
645
+ openWorldHint: false
646
+ };
647
+ function registerServerTools(server, api) {
648
+ server.registerTool(
649
+ "get_project",
650
+ {
651
+ title: "Get project",
652
+ description: "Show the Channlize project behind the server API key: name, ID, totals, the key's permissions and whether Slack is connected. Start here to check the setup.",
653
+ inputSchema: z2.object({}),
654
+ outputSchema: z2.object({ project: loose }),
655
+ annotations: READ
528
656
  },
529
- handler: async (args2, context) => {
530
- const client = createClient(context);
531
- const response = await client.submitContactForm({
532
- name: args2.name,
533
- email: args2.email,
534
- subject: args2.subject,
535
- message: args2.message,
536
- customFields: args2.customFields,
537
- pageUrl: args2.pageUrl,
538
- userAgent: args2.userAgent
539
- });
657
+ () => run(async () => {
658
+ const r = await api.request("GET", "/projects/me");
659
+ return { project: r.project };
660
+ })
661
+ );
662
+ server.registerTool(
663
+ "get_analytics",
664
+ {
665
+ title: "Get analytics",
666
+ description: "Daily support activity for the last 30 days (UTC): conversations created and resolved, customer and agent messages.",
667
+ inputSchema: z2.object({}),
668
+ outputSchema: z2.object({ analytics: loose }),
669
+ annotations: READ
670
+ },
671
+ () => run(async () => {
672
+ const r = await api.request(
673
+ "GET",
674
+ "/projects/analytics"
675
+ );
676
+ return { analytics: r.analytics };
677
+ })
678
+ );
679
+ server.registerTool(
680
+ "list_conversations",
681
+ {
682
+ title: "List conversations",
683
+ description: "List the organization's most recent support conversations (newest first, from the latest 50), optionally filtered by status or priority. Use get_conversation for the messages.",
684
+ inputSchema: z2.object({
685
+ status: z2.enum(STATUSES).optional().describe("Only conversations with this status"),
686
+ priority: z2.enum(PRIORITIES).optional().describe("Only conversations with this priority"),
687
+ limit: z2.number().int().min(1).max(50).default(20).describe("How many to return (1-50, default 20)")
688
+ }),
689
+ outputSchema: z2.object({
690
+ conversations: z2.array(conversationSummary),
691
+ returned: z2.number(),
692
+ matched: z2.number(),
693
+ scanned: z2.number()
694
+ }),
695
+ annotations: READ
696
+ },
697
+ ({ status, priority, limit }) => run(async () => {
698
+ const r = await api.request(
699
+ "GET",
700
+ "/conversations"
701
+ );
702
+ const all = r.conversations ?? [];
703
+ const matched = all.filter(
704
+ (c) => (!status || c.status === status) && (!priority || c.priority === priority)
705
+ );
706
+ const conversations = matched.slice(0, limit);
540
707
  return {
541
- success: response.success ?? true,
542
- conversationId: response.conversationId,
543
- message: response.message || "Contact form submitted successfully"
708
+ conversations,
709
+ returned: conversations.length,
710
+ matched: matched.length,
711
+ scanned: all.length
544
712
  };
545
- }
546
- },
547
- {
548
- name: "create_chat_session",
549
- description: "Create a new chat session for real-time messaging. Returns a session ID that can be used to send and receive messages.",
550
- inputSchema: {
551
- type: "object",
552
- properties: {
553
- metadata: {
554
- type: "object",
555
- description: "Custom metadata to attach to the session (e.g., page URL, user info)",
556
- additionalProperties: true
557
- }
713
+ })
714
+ );
715
+ server.registerTool(
716
+ "get_conversation",
717
+ {
718
+ title: "Get conversation",
719
+ description: "Read one conversation with its full message thread (customer, agent and system messages), status, priority and tags.",
720
+ inputSchema: z2.object({
721
+ conversationId: z2.string().uuid().describe("Conversation ID (from list_conversations)")
722
+ }),
723
+ outputSchema: z2.object({ conversation: loose }),
724
+ annotations: READ
725
+ },
726
+ ({ conversationId }) => run(async () => {
727
+ const conversation = await api.request(
728
+ "GET",
729
+ `/conversations/${encodeURIComponent(conversationId)}`
730
+ );
731
+ return { conversation };
732
+ })
733
+ );
734
+ server.registerTool(
735
+ "create_conversation",
736
+ {
737
+ title: "Create conversation",
738
+ description: "Open a new support conversation on behalf of a customer. It is posted to Slack (the given channel, else routing rules, else the default channel). Needs the write scope. Repeating the same customer, subject and message within 30 seconds returns the existing conversation.",
739
+ inputSchema: z2.object({
740
+ customerEmail: z2.email().describe("Customer's email address"),
741
+ customerName: z2.string().max(255).optional().describe("Customer's name"),
742
+ subject: z2.string().min(1).max(500).describe("Subject line"),
743
+ message: z2.string().min(1).describe("First message, from the customer"),
744
+ channel: z2.string().optional().describe("Slack channel ID to post to, overriding routing rules"),
745
+ tags: z2.array(z2.string()).max(20).optional().describe("Tags")
746
+ }),
747
+ outputSchema: z2.object({ conversation: loose }),
748
+ annotations: {
749
+ readOnlyHint: false,
750
+ destructiveHint: false,
751
+ idempotentHint: false,
752
+ openWorldHint: true
558
753
  }
559
754
  },
560
- handler: async (args2, context) => {
561
- const client = createClient(context);
562
- const response = await client.createChatSession(args2.metadata);
563
- return {
564
- success: true,
565
- session: {
566
- id: response.session.id,
567
- status: response.session.status,
568
- createdAt: response.session.createdAt,
569
- messageCount: response.session.messages?.length || 0
570
- },
571
- sseEndpoint: response.sseEndpoint,
572
- message: "Chat session created successfully"
573
- };
574
- }
575
- },
576
- {
577
- name: "send_message",
578
- description: "Send a message in an existing chat session. The message will be delivered to the Slack thread and the user can see agent replies in real-time.",
579
- inputSchema: {
580
- type: "object",
581
- properties: {
582
- sessionId: {
583
- type: "string",
584
- description: "The chat session ID"
585
- },
586
- content: {
587
- type: "string",
588
- description: "The message content to send"
589
- }
590
- },
591
- required: ["sessionId", "content"]
755
+ (args) => run(async () => {
756
+ const conversation = await api.request("POST", "/conversations", {
757
+ body: args
758
+ });
759
+ return { conversation };
760
+ })
761
+ );
762
+ server.registerTool(
763
+ "list_contacts",
764
+ {
765
+ title: "List contacts",
766
+ description: "List the customers who contacted the organization, with their conversation count. Paginate with limit/offset.",
767
+ inputSchema: z2.object({
768
+ limit: z2.number().int().min(1).max(100).default(50),
769
+ offset: z2.number().int().min(0).default(0)
770
+ }),
771
+ outputSchema: z2.object({ contacts: z2.array(loose), ...page }),
772
+ annotations: READ
592
773
  },
593
- handler: async (args2, context) => {
594
- const client = createClient(context);
595
- const response = await client.sendMessage(args2.sessionId, args2.content);
774
+ ({ limit, offset }) => run(async () => {
775
+ const r = await api.request("GET", "/contacts", {
776
+ query: { limit, offset }
777
+ });
596
778
  return {
597
- success: true,
598
- message: {
599
- id: response.message.id,
600
- content: response.message.content,
601
- timestamp: response.message.timestamp,
602
- author: response.message.author
603
- },
604
- sessionStatus: response.session.status,
605
- deliveryMessage: "Message sent successfully"
779
+ contacts: r.contacts ?? [],
780
+ total: Number(r.total ?? 0),
781
+ limit: Number(r.limit ?? limit),
782
+ offset: Number(r.offset ?? offset),
783
+ hasMore: Boolean(r.hasMore)
606
784
  };
607
- }
608
- },
609
- {
610
- name: "get_chat_messages",
611
- description: "Retrieve messages from a chat session. Useful for viewing conversation history.",
612
- inputSchema: {
613
- type: "object",
614
- properties: {
615
- sessionId: {
616
- type: "string",
617
- description: "The chat session ID"
618
- },
619
- limit: {
620
- type: "number",
621
- description: "Maximum number of messages to retrieve (default: 50)",
622
- default: 50
623
- },
624
- offset: {
625
- type: "number",
626
- description: "Number of messages to skip (default: 0)",
627
- default: 0
628
- }
629
- },
630
- required: ["sessionId"]
785
+ })
786
+ );
787
+ server.registerTool(
788
+ "get_contact",
789
+ {
790
+ title: "Get contact",
791
+ description: "Read one contact (customer) by ID.",
792
+ inputSchema: z2.object({
793
+ contactId: z2.string().uuid().describe("Contact ID (from list_contacts)")
794
+ }),
795
+ outputSchema: z2.object({ contact: loose }),
796
+ annotations: READ
631
797
  },
632
- handler: async (args2, context) => {
633
- const client = createClient(context);
634
- const messages = await client.getChatMessages(
635
- args2.sessionId,
636
- args2.limit || 50,
637
- args2.offset || 0
798
+ ({ contactId }) => run(async () => {
799
+ const contact = await api.request(
800
+ "GET",
801
+ `/contacts/${encodeURIComponent(contactId)}`
638
802
  );
803
+ return { contact };
804
+ })
805
+ );
806
+ server.registerTool(
807
+ "create_contact",
808
+ {
809
+ title: "Create contact",
810
+ description: "Add a customer by email, or return the existing one with that email. Needs the write scope.",
811
+ inputSchema: z2.object({
812
+ email: z2.email(),
813
+ name: z2.string().max(255).optional(),
814
+ company: z2.string().optional(),
815
+ phone: z2.string().optional(),
816
+ tags: z2.array(z2.string()).max(20).optional()
817
+ }),
818
+ outputSchema: z2.object({ contact: loose }),
819
+ annotations: {
820
+ readOnlyHint: false,
821
+ destructiveHint: false,
822
+ idempotentHint: true,
823
+ openWorldHint: false
824
+ }
825
+ },
826
+ (args) => run(async () => {
827
+ const contact = await api.request("POST", "/contacts", {
828
+ body: args
829
+ });
830
+ return { contact };
831
+ })
832
+ );
833
+ server.registerTool(
834
+ "notify_event",
835
+ {
836
+ title: "Send an Event to Slack",
837
+ description: 'Post an Event to Slack: the event key (e.g. "user.subscribed") picks the channel and message template set under Events in the dashboard, and `data` fills its {{placeholders}}. An unknown key is saved as a disabled draft and answered with status "skipped". Needs the write scope. Pass an idempotencyKey so a retry never posts twice.',
838
+ inputSchema: z2.object({
839
+ event: z2.string().max(100).regex(
840
+ EVENT_KEY,
841
+ "lowercase letters, digits, dots, underscores or dashes"
842
+ ).describe("Event key, e.g. user.subscribed"),
843
+ data: record2.optional().describe(
844
+ 'Template fields, e.g. { "username": "ada", "plan": "Pro" }'
845
+ ),
846
+ idempotencyKey: z2.string().min(1).max(255).optional().describe("Send at most once per key (retry-safe)"),
847
+ channel: z2.string().optional().describe("Slack channel ID instead of the event's channel")
848
+ }),
849
+ outputSchema: z2.looseObject({
850
+ id: z2.string(),
851
+ event: z2.string(),
852
+ status: z2.string(),
853
+ reason: z2.string().optional(),
854
+ duplicate: z2.boolean().optional()
855
+ }),
856
+ annotations: {
857
+ readOnlyHint: false,
858
+ destructiveHint: false,
859
+ idempotentHint: false,
860
+ openWorldHint: true
861
+ }
862
+ },
863
+ ({ event, data, idempotencyKey, channel }) => run(async () => {
864
+ const client = await api.notifier();
865
+ const r = await client.notify(event, data, { idempotencyKey, channel });
639
866
  return {
640
- success: true,
641
- sessionId: args2.sessionId,
642
- messageCount: messages.length,
643
- messages: messages.map((msg) => ({
644
- id: msg.id,
645
- content: msg.content,
646
- timestamp: msg.timestamp,
647
- author: msg.author,
648
- authorData: msg.authorData,
649
- hasAttachments: (msg.attachments?.length || 0) > 0
650
- }))
867
+ id: r.id,
868
+ event: r.event,
869
+ status: r.status,
870
+ ...r.reason ? { reason: r.reason } : {},
871
+ ...r.duplicate ? { duplicate: true } : {}
651
872
  };
652
- }
653
- },
654
- {
655
- name: "close_chat_session",
656
- description: "Close an active chat session. This marks the conversation as complete and stops real-time updates.",
657
- inputSchema: {
658
- type: "object",
659
- properties: {
660
- sessionId: {
661
- type: "string",
662
- description: "The chat session ID to close"
663
- }
664
- },
665
- required: ["sessionId"]
873
+ })
874
+ );
875
+ server.registerTool(
876
+ "list_event_definitions",
877
+ {
878
+ title: "List Events",
879
+ description: "List the Events configured on the project (key, name, Slack channel, message template, enabled), including disabled drafts created by unknown keys sent with notify_event.",
880
+ inputSchema: z2.object({}),
881
+ outputSchema: z2.object({
882
+ projectId: z2.string(),
883
+ definitions: z2.array(loose)
884
+ }),
885
+ annotations: READ
886
+ },
887
+ () => run(async () => {
888
+ const projectId = await api.projectId();
889
+ const r = await api.request(
890
+ "GET",
891
+ `/v1/projects/${encodeURIComponent(projectId)}/event-definitions`
892
+ );
893
+ return { projectId, definitions: r.definitions ?? [] };
894
+ })
895
+ );
896
+ server.registerTool(
897
+ "list_event_deliveries",
898
+ {
899
+ title: "List Event deliveries",
900
+ description: "Recent notify_event deliveries on the project, newest first, with their status (queued, sent, failed, skipped) and the reason when not sent. Use it to check why an Event did not reach Slack.",
901
+ inputSchema: z2.object({
902
+ definitionId: z2.string().uuid().optional().describe("Only this Event (ID from list_event_definitions)"),
903
+ limit: z2.number().int().min(1).max(100).default(20)
904
+ }),
905
+ outputSchema: z2.object({
906
+ projectId: z2.string(),
907
+ deliveries: z2.array(loose)
908
+ }),
909
+ annotations: READ
910
+ },
911
+ ({ definitionId, limit }) => run(async () => {
912
+ const projectId = await api.projectId();
913
+ const r = await api.request(
914
+ "GET",
915
+ `/v1/projects/${encodeURIComponent(projectId)}/event-deliveries`,
916
+ { query: { definitionId, limit } }
917
+ );
918
+ return { projectId, deliveries: r.deliveries ?? [] };
919
+ })
920
+ );
921
+ server.registerTool(
922
+ "query_logs",
923
+ {
924
+ title: "Query logs",
925
+ description: "Search log tracking (trackEvent / trackServerEvent / track_log), newest first. Filter by severity, source, text (matches the name and properties) and time range; paginate with limit/offset. Covers all projects unless projectId is given.",
926
+ inputSchema: z2.object({
927
+ severity: z2.enum(SEVERITIES).optional(),
928
+ source: z2.enum(["client_sdk", "server_api", "system"]).optional().describe(
929
+ "client_sdk = browser (widget key), server_api = server key"
930
+ ),
931
+ search: z2.string().max(200).optional().describe("Text to look for"),
932
+ from: z2.iso.datetime({ offset: true }).optional().describe("ISO 8601 start"),
933
+ to: z2.iso.datetime({ offset: true }).optional().describe("ISO 8601 end"),
934
+ projectId: z2.string().uuid().optional(),
935
+ limit: z2.number().int().min(1).max(100).default(50),
936
+ offset: z2.number().int().min(0).default(0)
937
+ }),
938
+ outputSchema: z2.object({ events: z2.array(loose), ...page }),
939
+ annotations: READ
666
940
  },
667
- handler: async (args2, context) => {
668
- const client = createClient(context);
669
- const session = await client.closeChatSession(args2.sessionId);
941
+ (args) => run(async () => {
942
+ const r = await api.request("GET", "/v1/events", { query: args });
670
943
  return {
671
- success: true,
672
- sessionId: session.id,
673
- status: session.status,
674
- closedAt: session.updatedAt,
675
- message: "Chat session closed successfully"
944
+ events: r.events ?? [],
945
+ total: Number(r.total ?? 0),
946
+ limit: Number(r.limit ?? args.limit),
947
+ offset: Number(r.offset ?? args.offset),
948
+ hasMore: Boolean(r.hasMore)
676
949
  };
677
- }
678
- }
679
- ];
680
- function getTool(name) {
681
- return tools.find((t) => t.name === name);
682
- }
683
- function listTools() {
684
- return tools;
685
- }
686
-
687
- // src/prompts/index.ts
688
- var prompts = [
689
- {
690
- name: "integrate-contact-form",
691
- description: "Guide for adding a Channlize contact form to a React application",
692
- arguments: [
693
- {
694
- name: "framework",
695
- description: "The React framework being used (e.g., Next.js, Vite, CRA)",
696
- required: false
697
- },
698
- {
699
- name: "styling",
700
- description: "The styling approach (e.g., Tailwind, CSS Modules, styled-components)",
701
- required: false
702
- }
703
- ],
704
- getPrompt: (args2) => {
705
- const framework = args2?.framework || "React";
706
- const styling = args2?.styling || "CSS";
707
- return `# Integrating Channlize Contact Form in ${framework}
708
-
709
- ## Step 1: Install Dependencies
710
-
711
- \`\`\`bash
712
- npm install @channlize/react
713
- \`\`\`
714
-
715
- ## Step 2: Configure Provider
716
-
717
- Wrap your application with the ChannlizeProvider at the root level:
718
-
719
- \`\`\`tsx
720
- import { ChannlizeProvider } from '@channlize/react';
721
-
722
- function App() {
723
- return (
724
- <ChannlizeProvider
725
- config={{
726
- apiKey: process.env.NEXT_PUBLIC_CHANNLIZE_API_KEY, // Your widget key
727
- projectId: process.env.NEXT_PUBLIC_CHANNLIZE_PROJECT_ID,
728
- baseUrl: process.env.NEXT_PUBLIC_CHANNLIZE_API_URL, // Optional
729
- }}
730
- >
731
- <YourAppContent />
732
- </ChannlizeProvider>
950
+ })
733
951
  );
734
- }
735
- \`\`\`
736
-
737
- ## Step 3: Add Form Component
738
-
739
- Create a contact page or component:
740
-
741
- \`\`\`tsx
742
- import { Form } from '@channlize/react';
743
-
744
- export default function ContactPage() {
745
- return (
746
- <div className="container mx-auto py-8">
747
- <h1>Contact Us</h1>
748
- <Form
749
- onSuccess={() => {
750
- alert('Thank you! We will get back to you soon.');
751
- }}
752
- onError={(error) => {
753
- console.error('Failed to submit:', error);
754
- }}
755
- customFields={[
756
- {
757
- name: 'department',
758
- label: 'Department',
759
- type: 'select',
760
- options: ['Sales', 'Support', 'Billing'],
761
- required: true,
762
- },
763
- ]}
764
- ${styling === "Tailwind" ? `className="max-w-2xl"` : ""}
765
- styles={{
766
- form: { maxWidth: '600px', margin: '0 auto' },
767
- button: {
768
- background: '#007bff',
769
- color: 'white',
770
- padding: '0.75rem 1.5rem',
771
- borderRadius: '0.5rem',
772
- },
773
- }}
774
- submitText="Send Message"
775
- successMessage="Thank you! We'll respond shortly."
776
- />
777
- </div>
778
- );
779
- }
780
- \`\`\`
781
-
782
- ## Step 4: Environment Variables
783
-
784
- Add to your \`.env.local\` or \`.env\`:
785
-
786
- \`\`\`
787
- NEXT_PUBLIC_CHANNLIZE_API_KEY=your_widget_key_here
788
- NEXT_PUBLIC_CHANNLIZE_PROJECT_ID=your_project_id_here
789
- NEXT_PUBLIC_CHANNLIZE_API_URL=http://api.channlize.com # Optional
790
- \`\`\`
791
-
792
- ## Step 5: Customize (Optional)
793
-
794
- ### Custom Fields
795
- Add custom fields to gather specific information:
796
-
797
- \`\`\`tsx
798
- customFields={[
799
- {
800
- name: 'company',
801
- label: 'Company Name',
802
- type: 'text',
803
- required: false,
804
- },
805
- {
806
- name: 'priority',
807
- label: 'Priority',
808
- type: 'select',
809
- options: ['Low', 'Medium', 'High'],
810
- required: true,
811
- },
812
- ]}
813
- \`\`\`
814
-
815
- ### Custom Styling with ${styling}
816
- ${styling === "Tailwind" ? "Use Tailwind classes with the className prop or override styles." : "Apply custom styles using the styles prop or external CSS."}
817
-
818
- ## Tips
819
-
820
- - Messages are sent to your configured Slack channel instantly
821
- - Email fallback is automatic if Slack delivery fails
822
- - Form includes built-in validation and error handling
823
- - All fields support accessibility features (ARIA labels, keyboard navigation)
824
-
825
- ## Next Steps
826
-
827
- - Set up Slack workspace integration in your Channlize dashboard
828
- - Configure channel routing rules
829
- - Add webhooks for custom workflows
830
- `;
831
- }
832
- },
833
- {
834
- name: "setup-live-chat",
835
- description: "Guide for implementing the Channlize live chat widget in a React application",
836
- arguments: [
837
- {
838
- name: "placement",
839
- description: 'Where to place the chat widget (e.g., "page", "popup", "sidebar")',
840
- required: false
952
+ server.registerTool(
953
+ "track_log",
954
+ {
955
+ title: "Record a log",
956
+ description: "Record a log line under Log tracking from a trusted server context (source server_api). Needs the write scope. For Slack messages about product events use notify_event instead.",
957
+ inputSchema: z2.object({
958
+ name: z2.string().min(1).max(255).describe("Log name, e.g. payment.failed"),
959
+ severity: z2.enum(SEVERITIES).default("info"),
960
+ properties: record2.optional().describe("Extra fields")
961
+ }),
962
+ outputSchema: z2.object({ id: z2.string(), projectId: z2.string() }),
963
+ annotations: {
964
+ readOnlyHint: false,
965
+ destructiveHint: false,
966
+ idempotentHint: false,
967
+ openWorldHint: false
841
968
  }
842
- ],
843
- getPrompt: (args2) => {
844
- const placement = args2?.placement || "page";
845
- return `# Setting Up Channlize Live Chat Widget
846
-
847
- ## Step 1: Install Dependencies
848
-
849
- \`\`\`bash
850
- npm install @channlize/react
851
- \`\`\`
852
-
853
- ## Step 2: Configure Provider
854
-
855
- Ensure your app is wrapped with ChannlizeProvider:
856
-
857
- \`\`\`tsx
858
- import { ChannlizeProvider } from '@channlize/react';
859
-
860
- function App() {
861
- return (
862
- <ChannlizeProvider
863
- config={{
864
- apiKey: process.env.NEXT_PUBLIC_CHANNLIZE_API_KEY,
865
- projectId: process.env.NEXT_PUBLIC_CHANNLIZE_PROJECT_ID,
866
- }}
867
- >
868
- <YourAppContent />
869
- </ChannlizeProvider>
870
- );
871
- }
872
- \`\`\`
873
-
874
- ## Step 3: Add Chat Component
875
-
876
- ${placement === "popup" ? `
877
- ### Popup Widget (Bottom-Right Corner)
878
-
879
- \`\`\`tsx
880
- import { useState } from 'react';
881
- import { Chat } from '@channlize/react';
882
-
883
- export function ChatWidget() {
884
- const [isOpen, setIsOpen] = useState(false);
885
-
886
- return (
887
- <>
888
- {/* Floating Button */}
889
- {!isOpen && (
890
- <button
891
- onClick={() => setIsOpen(true)}
892
- style={{
893
- position: 'fixed',
894
- bottom: '20px',
895
- right: '20px',
896
- width: '60px',
897
- height: '60px',
898
- borderRadius: '50%',
899
- background: '#007bff',
900
- color: 'white',
901
- border: 'none',
902
- fontSize: '24px',
903
- cursor: 'pointer',
904
- boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
905
- zIndex: 1000,
906
- }}
907
- >
908
- \u{1F4AC}
909
- </button>
910
- )}
911
-
912
- {/* Chat Window */}
913
- {isOpen && (
914
- <div
915
- style={{
916
- position: 'fixed',
917
- bottom: '20px',
918
- right: '20px',
919
- zIndex: 1000,
920
- boxShadow: '0 4px 24px rgba(0,0,0,0.15)',
921
- borderRadius: '12px',
922
- overflow: 'hidden',
923
- }}
924
- >
925
- <Chat
926
- autoStart={true}
927
- title="Customer Support"
928
- placeholder="Type your message..."
929
- onMessage={(message) => {
930
- console.log('New message:', message);
931
- }}
932
- styles={{
933
- container: {
934
- width: '380px',
935
- height: '600px',
936
- display: 'flex',
937
- flexDirection: 'column',
938
- background: 'white',
939
- },
940
- header: {
941
- background: '#007bff',
942
- color: 'white',
943
- padding: '1rem',
944
- display: 'flex',
945
- alignItems: 'center',
946
- },
947
- }}
948
- />
949
- <button
950
- onClick={() => setIsOpen(false)}
951
- style={{
952
- position: 'absolute',
953
- top: '10px',
954
- right: '10px',
955
- background: 'rgba(255,255,255,0.2)',
956
- border: 'none',
957
- color: 'white',
958
- cursor: 'pointer',
959
- fontSize: '20px',
960
- width: '30px',
961
- height: '30px',
962
- borderRadius: '50%',
963
- }}
964
- >
965
- \xD7
966
- </button>
967
- </div>
968
- )}
969
- </>
970
- );
971
- }
972
- \`\`\`
973
- ` : `
974
- ### Embedded Chat
975
-
976
- \`\`\`tsx
977
- import { Chat } from '@channlize/react';
978
-
979
- export default function SupportPage() {
980
- return (
981
- <div className="container mx-auto py-8">
982
- <h1>Customer Support</h1>
983
- <Chat
984
- autoStart={true}
985
- title="Live Chat Support"
986
- placeholder="How can we help you today?"
987
- onMessage={(message) => {
988
- // Handle incoming messages
989
- console.log('New message:', message);
990
- }}
991
- onError={(error) => {
992
- console.error('Chat error:', error);
993
- }}
994
- styles={{
995
- container: {
996
- width: '100%',
997
- maxWidth: '800px',
998
- height: '600px',
999
- margin: '0 auto',
1000
- border: '1px solid #e0e0e0',
1001
- borderRadius: '12px',
1002
- },
1003
- header: {
1004
- background: '#007bff',
1005
- color: 'white',
1006
- },
1007
- }}
1008
- maxHeight="600px"
1009
- />
1010
- </div>
1011
- );
1012
- }
1013
- \`\`\`
1014
- `}
1015
-
1016
- ## Step 4: Advanced Features
1017
-
1018
- ### Custom Metadata
1019
- Pass context about the user or page:
1020
-
1021
- \`\`\`tsx
1022
- <Chat
1023
- metadata={{
1024
- userId: user?.id,
1025
- userName: user?.name,
1026
- currentPage: window.location.pathname,
1027
- plan: 'pro',
1028
- }}
1029
- />
1030
- \`\`\`
1031
-
1032
- ### Event Handlers
1033
- React to chat events:
1034
-
1035
- \`\`\`tsx
1036
- <Chat
1037
- onMessage={(message) => {
1038
- // Track analytics
1039
- analytics.track('chat_message_received', {
1040
- author: message.author,
1041
- timestamp: message.timestamp,
1042
- });
1043
- }}
1044
- onError={(error) => {
1045
- // Report errors
1046
- errorReporting.log(error);
1047
- }}
1048
- />
1049
- \`\`\`
1050
-
1051
- ## Real-Time Features
1052
-
1053
- - Messages are delivered instantly via Server-Sent Events (SSE)
1054
- - Agent presence indicators show when team is active
1055
- - File attachments supported (images, documents)
1056
- - Automatic reconnection on connection loss
1057
- - Message history persisted across sessions
1058
-
1059
- ## Tips
1060
-
1061
- - Chat sessions are automatically created on first message
1062
- - Sessions remain active until explicitly closed
1063
- - Messages are threaded in Slack for team collaboration
1064
- - Supports multiple concurrent chat sessions per project
1065
-
1066
- ## Next Steps
1067
-
1068
- - Configure Slack notifications for new chats
1069
- - Set up auto-responses for common questions
1070
- - Integrate with your analytics platform
1071
- - Add custom routing based on user context
1072
- `;
1073
- }
1074
- },
1075
- {
1076
- name: "customize-styling",
1077
- description: "Guide for theming and customizing Channlize components",
1078
- getPrompt: () => `# Customizing Channlize Component Styling
1079
-
1080
- Channlize components are designed to be highly customizable to match your brand.
1081
-
1082
- ## Styling Approaches
1083
-
1084
- ### 1. Inline Styles with \`styles\` Prop
1085
-
1086
- The most direct way to customize appearance:
1087
-
1088
- \`\`\`tsx
1089
- <Form
1090
- styles={{
1091
- form: {
1092
- maxWidth: '600px',
1093
- margin: '0 auto',
1094
- padding: '2rem',
1095
- background: '#f9fafb',
1096
- borderRadius: '8px',
1097
- },
1098
- field: {
1099
- marginBottom: '1.5rem',
1100
- },
1101
- label: {
1102
- display: 'block',
1103
- marginBottom: '0.5rem',
1104
- fontWeight: '600',
1105
- color: '#374151',
1106
- },
1107
- input: {
1108
- width: '100%',
1109
- padding: '0.75rem',
1110
- border: '1px solid #d1d5db',
1111
- borderRadius: '6px',
1112
- fontSize: '1rem',
1113
- },
1114
- textarea: {
1115
- width: '100%',
1116
- padding: '0.75rem',
1117
- border: '1px solid #d1d5db',
1118
- borderRadius: '6px',
1119
- fontSize: '1rem',
1120
- minHeight: '120px',
1121
- },
1122
- button: {
1123
- background: '#3b82f6',
1124
- color: 'white',
1125
- padding: '0.75rem 2rem',
1126
- border: 'none',
1127
- borderRadius: '6px',
1128
- fontSize: '1rem',
1129
- fontWeight: '600',
1130
- cursor: 'pointer',
1131
- transition: 'background 0.2s',
1132
- },
1133
- error: {
1134
- color: '#ef4444',
1135
- fontSize: '0.875rem',
1136
- marginTop: '0.5rem',
1137
- },
1138
- success: {
1139
- background: '#ecfdf5',
1140
- border: '1px solid #10b981',
1141
- padding: '1.5rem',
1142
- borderRadius: '6px',
1143
- color: '#065f46',
1144
- },
1145
- }}
1146
- />
1147
- \`\`\`
1148
-
1149
- ### 2. CSS Classes with \`className\` Prop
1150
-
1151
- Use your existing CSS or CSS-in-JS solution:
1152
-
1153
- \`\`\`tsx
1154
- <Form className="custom-form" />
1155
- \`\`\`
1156
-
1157
- \`\`\`css
1158
- /* styles.css */
1159
- .channlize-form {
1160
- max-width: 600px;
1161
- margin: 0 auto;
1162
- }
1163
-
1164
- .channlize-field {
1165
- margin-bottom: 1.5rem;
1166
- }
1167
-
1168
- .channlize-field label {
1169
- font-weight: 600;
1170
- color: var(--primary-text);
1171
- }
1172
-
1173
- .channlize-field input,
1174
- .channlize-field textarea,
1175
- .channlize-field select {
1176
- border: 1px solid var(--border-color);
1177
- border-radius: 6px;
1178
- padding: 0.75rem;
1179
- }
1180
-
1181
- .channlize-form button[type="submit"] {
1182
- background: var(--primary-color);
1183
- color: white;
1184
- }
1185
- \`\`\`
1186
-
1187
- ### 3. Tailwind CSS
1188
-
1189
- Combine with Tailwind utility classes:
1190
-
1191
- \`\`\`tsx
1192
- <Form
1193
- className="max-w-2xl mx-auto p-8"
1194
- styles={{
1195
- input: {
1196
- // Tailwind styles as objects don't work well
1197
- // Use className on wrapper or global CSS
1198
969
  },
1199
- }}
1200
- />
1201
- \`\`\`
1202
-
1203
- \`\`\`css
1204
- /* globals.css with @layer */
1205
- @layer components {
1206
- .channlize-field input {
1207
- @apply w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500;
1208
- }
1209
-
1210
- .channlize-field textarea {
1211
- @apply w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500;
1212
- }
1213
-
1214
- .channlize-form button[type="submit"] {
1215
- @apply bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 px-6 rounded-lg transition;
1216
- }
970
+ ({ name, severity, properties }) => run(async () => {
971
+ const projectId = await api.projectId();
972
+ const r = await api.request("POST", "/v1/events", {
973
+ body: { projectId, name, severity, properties }
974
+ });
975
+ return { id: r.id, projectId };
976
+ })
977
+ );
1217
978
  }
1218
- \`\`\`
1219
-
1220
- ## Styling Chat
1221
-
1222
- \`\`\`tsx
1223
- <Chat
1224
- styles={{
1225
- container: {
1226
- border: '1px solid #e5e7eb',
1227
- borderRadius: '12px',
1228
- overflow: 'hidden',
1229
- boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
1230
- },
1231
- header: {
1232
- background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
1233
- color: 'white',
1234
- padding: '1rem',
1235
- },
1236
- messages: {
1237
- background: '#f9fafb',
1238
- padding: '1rem',
979
+ var VISITOR = {
980
+ readOnlyHint: false,
981
+ destructiveHint: false,
982
+ idempotentHint: false,
983
+ openWorldHint: true
984
+ };
985
+ function registerWidgetTools(server, api) {
986
+ server.registerTool(
987
+ "submit_contact_form",
988
+ {
989
+ title: "Submit contact form",
990
+ description: "Submit the website contact form as a visitor (widget key). Creates a conversation that is posted to Slack. Useful to test the setup end to end.",
991
+ inputSchema: z2.object({
992
+ name: z2.string().min(1).describe("Visitor's name"),
993
+ email: z2.email().describe("Visitor's email"),
994
+ subject: z2.string().optional(),
995
+ message: z2.string().min(1),
996
+ pageUrl: z2.url().optional().describe("Page the form was sent from")
997
+ }),
998
+ outputSchema: z2.looseObject({
999
+ conversationId: z2.string().optional(),
1000
+ message: z2.string().optional()
1001
+ }),
1002
+ annotations: VISITOR
1239
1003
  },
1240
- messageUser: {
1241
- background: '#3b82f6',
1242
- color: 'white',
1243
- padding: '0.75rem',
1244
- borderRadius: '12px 12px 4px 12px',
1245
- maxWidth: '70%',
1246
- marginLeft: 'auto',
1004
+ (args) => run(async () => {
1005
+ const r = await api.widget().submitContactForm(args);
1006
+ return { conversationId: r.conversationId, message: r.message };
1007
+ })
1008
+ );
1009
+ server.registerTool(
1010
+ "create_chat_session",
1011
+ {
1012
+ title: "Start chat session",
1013
+ description: "Start a live chat session as a website visitor (widget key). Returns the session ID for send_message.",
1014
+ inputSchema: z2.object({
1015
+ metadata: record2.optional().describe("Metadata to attach (e.g. user info)")
1016
+ }),
1017
+ outputSchema: z2.looseObject({
1018
+ sessionId: z2.string(),
1019
+ status: z2.string()
1020
+ }),
1021
+ annotations: VISITOR
1247
1022
  },
1248
- messageAgent: {
1249
- background: 'white',
1250
- color: '#1f2937',
1251
- padding: '0.75rem',
1252
- borderRadius: '12px 12px 12px 4px',
1253
- maxWidth: '70%',
1254
- border: '1px solid #e5e7eb',
1023
+ ({ metadata }) => run(async () => {
1024
+ const r = await api.widget().createChatSession(metadata);
1025
+ return {
1026
+ sessionId: r.session.id,
1027
+ status: r.session.status,
1028
+ createdAt: String(r.session.createdAt)
1029
+ };
1030
+ })
1031
+ );
1032
+ server.registerTool(
1033
+ "send_message",
1034
+ {
1035
+ title: "Send chat message",
1036
+ description: "Send a visitor message in a chat session (widget key); it is posted to the Slack thread.",
1037
+ inputSchema: z2.object({
1038
+ sessionId: z2.string().min(1).describe("From create_chat_session"),
1039
+ content: z2.string().min(1).max(5e3)
1040
+ }),
1041
+ outputSchema: z2.looseObject({
1042
+ messageId: z2.string(),
1043
+ sessionStatus: z2.string()
1044
+ }),
1045
+ annotations: VISITOR
1255
1046
  },
1256
- input: {
1257
- border: '1px solid #d1d5db',
1258
- borderRadius: '6px',
1259
- padding: '0.75rem',
1260
- fontSize: '1rem',
1047
+ ({ sessionId, content }) => run(async () => {
1048
+ const r = await api.widget().sendMessage(sessionId, content);
1049
+ return { messageId: r.message.id, sessionStatus: r.session.status };
1050
+ })
1051
+ );
1052
+ server.registerTool(
1053
+ "get_chat_messages",
1054
+ {
1055
+ title: "Read chat messages",
1056
+ description: "Read a chat session's messages, including agent replies from Slack (widget key).",
1057
+ inputSchema: z2.object({
1058
+ sessionId: z2.string().min(1),
1059
+ limit: z2.number().int().min(1).max(100).default(50),
1060
+ offset: z2.number().int().min(0).default(0)
1061
+ }),
1062
+ outputSchema: z2.object({
1063
+ sessionId: z2.string(),
1064
+ messages: z2.array(loose)
1065
+ }),
1066
+ annotations: { ...READ, openWorldHint: true }
1261
1067
  },
1262
- button: {
1263
- background: '#3b82f6',
1264
- color: 'white',
1265
- padding: '0.75rem 1.5rem',
1266
- borderRadius: '6px',
1267
- border: 'none',
1268
- cursor: 'pointer',
1068
+ ({ sessionId, limit, offset }) => run(async () => {
1069
+ const messages = await api.widget().getChatMessages(sessionId, limit, offset);
1070
+ return {
1071
+ sessionId,
1072
+ messages: messages.map((m) => ({
1073
+ id: m.id,
1074
+ author: m.author,
1075
+ authorName: m.authorData?.name,
1076
+ content: m.content,
1077
+ timestamp: String(m.timestamp),
1078
+ attachments: m.attachments?.length ?? 0
1079
+ }))
1080
+ };
1081
+ })
1082
+ );
1083
+ server.registerTool(
1084
+ "close_chat_session",
1085
+ {
1086
+ title: "End chat session",
1087
+ description: "End a chat session as the visitor (widget key).",
1088
+ inputSchema: z2.object({ sessionId: z2.string().min(1) }),
1089
+ outputSchema: z2.looseObject({
1090
+ sessionId: z2.string(),
1091
+ status: z2.string()
1092
+ }),
1093
+ annotations: { ...VISITOR, idempotentHint: true }
1269
1094
  },
1270
- }}
1271
- />
1272
- \`\`\`
1273
-
1274
- ## CSS Class Reference
1275
-
1276
- All Channlize components use semantic CSS classes:
1277
-
1278
- **Form:**
1279
- - \`.channlize-form\` - Form container
1280
- - \`.channlize-form-success\` - Success message container
1281
- - \`.channlize-field\` - Field wrapper
1282
- - \`.channlize-error\` - Error message
1283
-
1284
- **Chat:**
1285
- - \`.channlize-chat-container\` - Main container
1286
- - \`.channlize-chat-header\` - Header section
1287
- - \`.channlize-chat-messages\` - Messages container
1288
- - \`.channlize-message\` - Individual message
1289
- - \`.channlize-message-user\` - User message
1290
- - \`.channlize-message-agent\` - Agent message
1291
- - \`.channlize-chat-input-container\` - Input area
1292
-
1293
- ## Dark Mode Example
1294
-
1295
- \`\`\`tsx
1296
- const darkStyles = {
1297
- form: {
1298
- background: '#1f2937',
1299
- color: '#f9fafb',
1300
- },
1301
- input: {
1302
- background: '#374151',
1303
- color: '#f9fafb',
1304
- border: '1px solid #4b5563',
1305
- },
1306
- button: {
1307
- background: '#3b82f6',
1308
- color: 'white',
1309
- },
1310
- };
1311
-
1312
- <Form styles={darkStyles} />
1313
- \`\`\`
1314
- `
1315
- },
1316
- {
1317
- name: "handle-webhooks",
1318
- description: "Guide for integrating Channlize webhooks into custom workflows",
1319
- getPrompt: () => `# Integrating Channlize Webhooks
1320
-
1321
- Channlize can send webhook events to your backend for custom workflows and integrations.
1322
-
1323
- ## Available Webhook Events
1324
-
1325
- - \`conversation.created\` - New contact form or chat session started
1326
- - \`message.created\` - New message in a conversation
1327
- - \`conversation.closed\` - Conversation marked as closed
1328
- - \`agent.assigned\` - Agent assigned to conversation
1329
-
1330
- ## Setting Up Webhooks
1331
-
1332
- ### 1. Configure Webhook URL
1333
-
1334
- In your Channlize dashboard:
1335
- 1. Navigate to Project Settings \u2192 Webhooks
1336
- 2. Add your webhook endpoint URL
1337
- 3. Select which events to receive
1338
- 4. Save your secret key for verification
1339
-
1340
- ### 2. Create Webhook Handler (Next.js API Route)
1341
-
1342
- \`\`\`typescript
1343
- // app/api/webhooks/channlize/route.ts
1344
- import { NextRequest, NextResponse } from 'next/server';
1345
- import crypto from 'crypto';
1346
-
1347
- const WEBHOOK_SECRET = process.env.CHANNLIZE_WEBHOOK_SECRET!;
1348
-
1349
- function verifySignature(payload: string, signature: string): boolean {
1350
- const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
1351
- const digest = hmac.update(payload).digest('hex');
1352
- return crypto.timingSafeEqual(
1353
- Buffer.from(signature),
1354
- Buffer.from(digest)
1095
+ ({ sessionId }) => run(async () => {
1096
+ const s = await api.widget().closeChatSession(sessionId);
1097
+ return { sessionId: s.id ?? sessionId, status: s.status };
1098
+ })
1355
1099
  );
1356
1100
  }
1357
1101
 
1358
- export async function POST(req: NextRequest) {
1359
- const signature = req.headers.get('x-channlize-signature');
1360
- const payload = await req.text();
1361
-
1362
- if (!signature || !verifySignature(payload, signature)) {
1363
- return NextResponse.json(
1364
- { error: 'Invalid signature' },
1365
- { status: 401 }
1366
- );
1367
- }
1368
-
1369
- const event = JSON.parse(payload);
1370
-
1371
- // Handle different event types
1372
- switch (event.type) {
1373
- case 'conversation.created':
1374
- await handleNewConversation(event.data);
1375
- break;
1376
-
1377
- case 'message.created':
1378
- await handleNewMessage(event.data);
1379
- break;
1380
-
1381
- case 'conversation.closed':
1382
- await handleConversationClosed(event.data);
1383
- break;
1384
-
1385
- default:
1386
- console.log('Unhandled event type:', event.type);
1387
- }
1388
-
1389
- return NextResponse.json({ received: true });
1390
- }
1391
-
1392
- async function handleNewConversation(data: any) {
1393
- // Example: Log to analytics
1394
- await analytics.track('support_conversation_started', {
1395
- conversationId: data.conversation.id,
1396
- type: data.conversation.type, // 'contact_form' or 'chat'
1397
- customerEmail: data.conversation.email,
1398
- });
1399
-
1400
- // Example: Notify sales team for high-value customers
1401
- if (data.conversation.metadata?.customerTier === 'enterprise') {
1402
- await notifySalesTeam({
1403
- conversationId: data.conversation.id,
1404
- customerEmail: data.conversation.email,
1405
- message: data.conversation.initialMessage,
1406
- });
1102
+ // src/http.ts
1103
+ var import_node_http = require("http");
1104
+ var import_server = require("@modelcontextprotocol/server");
1105
+ var LOCAL_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1"]);
1106
+ async function toRequest(req, origin) {
1107
+ const headers = new Headers();
1108
+ for (const [k, v] of Object.entries(req.headers)) {
1109
+ if (Array.isArray(v)) v.forEach((x) => headers.append(k, x));
1110
+ else if (v !== void 0) headers.set(k, v);
1407
1111
  }
1408
- }
1409
-
1410
- async function handleNewMessage(data: any) {
1411
- // Example: Auto-tag based on content
1412
- const message = data.message.content.toLowerCase();
1413
-
1414
- if (message.includes('bug') || message.includes('error')) {
1415
- await tagConversation(data.conversation.id, 'bug');
1416
- }
1417
-
1418
- if (message.includes('billing') || message.includes('payment')) {
1419
- await tagConversation(data.conversation.id, 'billing');
1420
- await assignToDepartment(data.conversation.id, 'billing');
1421
- }
1422
-
1423
- // Example: Sentiment analysis
1424
- const sentiment = await analyzeSentiment(message);
1425
- if (sentiment === 'negative') {
1426
- await escalateConversation(data.conversation.id, 'high');
1112
+ let body;
1113
+ if (req.method !== "GET" && req.method !== "HEAD") {
1114
+ const chunks = [];
1115
+ for await (const chunk of req) chunks.push(chunk);
1116
+ body = new Uint8Array(Buffer.concat(chunks));
1427
1117
  }
1428
- }
1429
-
1430
- async function handleConversationClosed(data: any) {
1431
- // Example: Send satisfaction survey
1432
- await sendSurveyEmail({
1433
- to: data.conversation.email,
1434
- conversationId: data.conversation.id,
1435
- resolutionTime: data.metrics.resolutionTime,
1118
+ const controller = new AbortController();
1119
+ req.on("close", () => {
1120
+ if (!req.complete) controller.abort();
1436
1121
  });
1437
-
1438
- // Example: Update CRM
1439
- await updateCRM({
1440
- email: data.conversation.email,
1441
- lastInteraction: new Date(),
1442
- supportTicketId: data.conversation.id,
1443
- resolved: true,
1122
+ return new Request(new URL(req.url ?? "/", origin), {
1123
+ method: req.method,
1124
+ headers,
1125
+ body,
1126
+ signal: controller.signal
1444
1127
  });
1445
1128
  }
1446
- \`\`\`
1447
-
1448
- ### 3. Express.js Example
1449
-
1450
- \`\`\`typescript
1451
- import express from 'express';
1452
- import crypto from 'crypto';
1453
-
1454
- const app = express();
1455
-
1456
- // Raw body needed for signature verification
1457
- app.use('/webhooks/channlize', express.raw({ type: 'application/json' }));
1458
-
1459
- app.post('/webhooks/channlize', (req, res) => {
1460
- const signature = req.headers['x-channlize-signature'];
1461
- const payload = req.body.toString();
1462
-
1463
- // Verify signature
1464
- const hmac = crypto.createHmac('sha256', process.env.CHANNLIZE_WEBHOOK_SECRET);
1465
- const digest = hmac.update(payload).digest('hex');
1466
-
1467
- if (signature !== digest) {
1468
- return res.status(401).json({ error: 'Invalid signature' });
1469
- }
1470
-
1471
- const event = JSON.parse(payload);
1472
-
1473
- // Handle event
1474
- handleEvent(event);
1475
-
1476
- res.json({ received: true });
1477
- });
1478
- \`\`\`
1479
-
1480
- ## Webhook Event Payload Examples
1481
-
1482
- ### conversation.created
1483
-
1484
- \`\`\`json
1485
- {
1486
- "type": "conversation.created",
1487
- "timestamp": "2025-01-15T10:00:00Z",
1488
- "data": {
1489
- "conversation": {
1490
- "id": "conv_123",
1491
- "type": "contact_form",
1492
- "status": "open",
1493
- "email": "customer@example.com",
1494
- "name": "John Doe",
1495
- "initialMessage": "I need help with...",
1496
- "metadata": {
1497
- "pageUrl": "https://example.com/contact",
1498
- "userAgent": "Mozilla/5.0..."
1499
- },
1500
- "createdAt": "2025-01-15T10:00:00Z"
1501
- }
1502
- }
1503
- }
1504
- \`\`\`
1505
-
1506
- ### message.created
1507
-
1508
- \`\`\`json
1509
- {
1510
- "type": "message.created",
1511
- "timestamp": "2025-01-15T10:05:00Z",
1512
- "data": {
1513
- "message": {
1514
- "id": "msg_456",
1515
- "conversationId": "conv_123",
1516
- "content": "Thanks for reaching out!",
1517
- "author": "agent",
1518
- "authorData": {
1519
- "name": "Sarah",
1520
- "email": "sarah@example.com"
1521
- },
1522
- "timestamp": "2025-01-15T10:05:00Z"
1523
- },
1524
- "conversation": {
1525
- "id": "conv_123",
1526
- "status": "open"
1129
+ async function startHttpServer(factory, options = {}) {
1130
+ const host = options.host ?? "127.0.0.1";
1131
+ const port = options.port ?? 3333;
1132
+ const path = options.path ?? "/mcp";
1133
+ const local = LOCAL_HOSTS.has(host);
1134
+ const handler = (0, import_server.createMcpHandler)(() => factory(), {
1135
+ onerror: (err) => console.error("[channlize-mcp]", err.message)
1136
+ });
1137
+ const server = (0, import_node_http.createServer)(async (req, res) => {
1138
+ try {
1139
+ const request = await toRequest(
1140
+ req,
1141
+ `http://${req.headers.host ?? `${host}:${port}`}`
1142
+ );
1143
+ const url2 = new URL(request.url);
1144
+ if (url2.pathname !== path) {
1145
+ res.writeHead(404, { "Content-Type": "text/plain" }).end(`Not found: use ${path}`);
1146
+ return;
1147
+ }
1148
+ const rejected = local ? (0, import_server.hostHeaderValidationResponse)(request, (0, import_server.localhostAllowedHostnames)()) ?? (0, import_server.originValidationResponse)(request, (0, import_server.localhostAllowedOrigins)()) : void 0;
1149
+ const response = rejected ?? await handler.fetch(request);
1150
+ res.writeHead(response.status, Object.fromEntries(response.headers));
1151
+ if (response.body) {
1152
+ for await (const chunk of response.body) {
1153
+ res.write(chunk);
1154
+ }
1155
+ }
1156
+ res.end();
1157
+ } catch (err) {
1158
+ console.error("[channlize-mcp]", err.message);
1159
+ if (!res.headersSent)
1160
+ res.writeHead(500, { "Content-Type": "text/plain" });
1161
+ res.end("Internal server error");
1527
1162
  }
1528
- }
1163
+ });
1164
+ await new Promise((resolve, reject) => {
1165
+ server.once("error", reject);
1166
+ server.listen(port, host, () => resolve());
1167
+ });
1168
+ const address = server.address();
1169
+ const actualPort = typeof address === "object" && address ? address.port : port;
1170
+ const shownHost = host.includes(":") ? `[${host}]` : host;
1171
+ return {
1172
+ url: `http://${shownHost}:${actualPort}${path}`,
1173
+ server,
1174
+ close: () => new Promise((r) => server.close(() => r()))
1175
+ };
1529
1176
  }
1530
- \`\`\`
1531
-
1532
- ## Use Cases
1533
-
1534
- 1. **CRM Integration**: Sync conversations to Salesforce, HubSpot, etc.
1535
- 2. **Analytics**: Track support metrics in your data warehouse
1536
- 3. **Auto-routing**: Assign conversations based on content analysis
1537
- 4. **Notifications**: Alert teams via SMS, PagerDuty for urgent issues
1538
- 5. **AI Enhancement**: Feed messages to AI for sentiment/intent analysis
1539
- 6. **Billing**: Track support usage for metered billing
1540
1177
 
1541
- ## Security Best Practices
1542
-
1543
- - Always verify webhook signatures
1544
- - Use HTTPS endpoints only
1545
- - Rotate webhook secrets periodically
1546
- - Implement rate limiting on webhook endpoints
1547
- - Log all webhook events for audit trail
1548
- `
1178
+ // src/index.ts
1179
+ var VERSION = true ? "0.3.0" : "0.0.0";
1180
+ var INSTRUCTIONS = `Channlize routes website conversations and app events to the team's Slack.
1181
+ - Inbox: list_conversations / get_conversation, then contacts. Replies and status changes happen in Slack or the dashboard.
1182
+ - Events (notify_event) post a Slack message per event key; list_event_definitions and list_event_deliveries explain what was sent or skipped.
1183
+ - Logs: query_logs searches log tracking; track_log records one.
1184
+ - Write tools need a server API key with the write scope; a 403 means the key is read-only.`;
1185
+ function buildServer(config, info = {}) {
1186
+ const api = new ChannlizeApi(resolveConfig(config));
1187
+ const server = new import_server2.McpServer(
1188
+ { name: info.name ?? "channlize", version: info.version ?? VERSION },
1189
+ { instructions: INSTRUCTIONS }
1190
+ );
1191
+ if (api.hasServerKey) {
1192
+ registerServerTools(server, api);
1193
+ registerServerPrompts(server);
1549
1194
  }
1550
- ];
1551
- function getPrompt(name) {
1552
- return prompts.find((p) => p.name === name);
1553
- }
1554
- function listPrompts() {
1555
- return prompts;
1195
+ if (api.hasWidgetKey) registerWidgetTools(server, api);
1196
+ registerResources(server, api);
1197
+ registerGuidePrompts(server);
1198
+ return server;
1556
1199
  }
1557
-
1558
- // src/index.ts
1559
1200
  var ChannlizeMCPServer = class {
1560
1201
  server;
1561
1202
  config;
1203
+ info;
1562
1204
  constructor(options = {}) {
1563
- this.config = options.config;
1564
- this.server = new import_server.Server(
1565
- {
1566
- name: options.name || "channlize-mcp",
1567
- version: options.version || "0.1.0"
1568
- },
1569
- {
1570
- capabilities: {
1571
- resources: {},
1572
- tools: {},
1573
- prompts: {}
1574
- }
1575
- }
1576
- );
1577
- this.setupHandlers();
1578
- }
1579
- setupHandlers() {
1580
- this.server.setRequestHandler(import_types.ListResourcesRequestSchema, async () => {
1581
- const resources2 = listResources();
1582
- return {
1583
- resources: resources2.map((r) => ({
1584
- uri: r.uri,
1585
- name: r.name,
1586
- description: r.description,
1587
- mimeType: r.mimeType
1588
- }))
1589
- };
1590
- });
1591
- this.server.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
1592
- const resource = getResource(request.params.uri);
1593
- if (!resource) {
1594
- throw new Error(`Resource not found: ${request.params.uri}`);
1595
- }
1596
- return {
1597
- contents: [
1598
- {
1599
- uri: resource.uri,
1600
- mimeType: resource.mimeType,
1601
- text: resource.getText()
1602
- }
1603
- ]
1604
- };
1605
- });
1606
- this.server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
1607
- const tools2 = listTools();
1608
- return {
1609
- tools: tools2.map((t) => ({
1610
- name: t.name,
1611
- description: t.description,
1612
- inputSchema: t.inputSchema
1613
- }))
1614
- };
1615
- });
1616
- this.server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
1617
- const tool = getTool(request.params.name);
1618
- if (!tool) {
1619
- throw new Error(`Tool not found: ${request.params.name}`);
1620
- }
1621
- if (!this.config?.apiKey) {
1622
- throw new Error(
1623
- "Channlize API key required. Please configure the MCP server with your API credentials."
1624
- );
1625
- }
1626
- const context = {
1627
- config: this.config
1628
- };
1629
- try {
1630
- const result = await tool.handler(
1631
- request.params.arguments || {},
1632
- context
1633
- );
1634
- return {
1635
- content: [
1636
- {
1637
- type: "text",
1638
- text: JSON.stringify(result, null, 2)
1639
- }
1640
- ]
1641
- };
1642
- } catch (error) {
1643
- return {
1644
- content: [
1645
- {
1646
- type: "text",
1647
- text: JSON.stringify(
1648
- {
1649
- success: false,
1650
- error: error.message || "Unknown error",
1651
- code: error.code,
1652
- statusCode: error.statusCode
1653
- },
1654
- null,
1655
- 2
1656
- )
1657
- }
1658
- ],
1659
- isError: true
1660
- };
1661
- }
1662
- });
1663
- this.server.setRequestHandler(import_types.ListPromptsRequestSchema, async () => {
1664
- const prompts2 = listPrompts();
1665
- return {
1666
- prompts: prompts2.map((p) => ({
1667
- name: p.name,
1668
- description: p.description,
1669
- arguments: p.arguments
1670
- }))
1671
- };
1672
- });
1673
- this.server.setRequestHandler(import_types.GetPromptRequestSchema, async (request) => {
1674
- const prompt = getPrompt(request.params.name);
1675
- if (!prompt) {
1676
- throw new Error(`Prompt not found: ${request.params.name}`);
1677
- }
1678
- const args2 = request.params.arguments;
1679
- const text = prompt.getPrompt(args2);
1680
- return {
1681
- description: prompt.description,
1682
- messages: [
1683
- {
1684
- role: "user",
1685
- content: {
1686
- type: "text",
1687
- text
1688
- }
1689
- }
1690
- ]
1691
- };
1692
- });
1693
- }
1694
- /**
1695
- * Set or update the Channlize configuration
1696
- */
1697
- setConfig(config2) {
1698
- this.config = config2;
1205
+ this.config = resolveConfig(options.config ?? {});
1206
+ this.info = { name: options.name, version: options.version };
1207
+ this.server = buildServer(this.config, this.info);
1699
1208
  }
1700
- /**
1701
- * Get the underlying MCP server instance
1702
- */
1209
+ /** The underlying `McpServer`. */
1703
1210
  getServer() {
1704
1211
  return this.server;
1705
1212
  }
1706
- /**
1707
- * Start the server with stdio transport
1708
- */
1213
+ /** Serve over stdio (what desktop and IDE clients launch). */
1709
1214
  async runStdio() {
1710
- const transport = new import_stdio.StdioServerTransport();
1711
- await this.server.connect(transport);
1215
+ await this.server.connect(new import_stdio.StdioServerTransport());
1712
1216
  }
1713
1217
  /**
1714
- * Close the server
1218
+ * Serve Streamable HTTP at `http://<host>:<port>/mcp` (default
1219
+ * 127.0.0.1:3333). Each request gets a fresh server; nothing is shared.
1715
1220
  */
1221
+ async runHttp(options = {}) {
1222
+ return startHttpServer(() => buildServer(this.config, this.info), options);
1223
+ }
1716
1224
  async close() {
1717
1225
  await this.server.close();
1718
1226
  }
1719
1227
  };
1720
1228
 
1721
1229
  // src/server.ts
1722
- var args = process.argv.slice(2);
1723
- var getArg = (name) => {
1724
- const index = args.findIndex(
1725
- (arg) => arg === `--${name}` || arg === `-${name[0]}`
1726
- );
1727
- return index >= 0 && index + 1 < args.length ? args[index + 1] : void 0;
1728
- };
1729
- if (args.includes("--help") || args.includes("-h")) {
1730
- console.log(`
1731
- Channlize MCP Server
1230
+ var HELP = `Channlize MCP server ${VERSION}
1732
1231
 
1733
1232
  Usage: channlize-mcp [options]
1734
1233
 
1234
+ Keys (flags override environment variables):
1235
+ --server-api-key <key> Server API key, ch_live_\u2026 CHANNLIZE_SERVER_API_KEY
1236
+ --widget-key <key> Public widget key, wk_\u2026 CHANNLIZE_WIDGET_KEY
1237
+ --api-key <key> Either kind, by prefix CHANNLIZE_API_KEY
1238
+ --project-id <id> Project (default: the key's first project)
1239
+ CHANNLIZE_PROJECT_ID
1735
1240
  Options:
1736
- --api-key <key> Channlize API key (widget key) [required]
1737
- --project-id <id> Channlize project ID
1738
- --base-url <url> Channlize API base URL (default: https://api.channlize.com)
1739
- --timeout <ms> Request timeout in milliseconds (default: 10000)
1740
- -h, --help Show this help message
1741
-
1742
- Environment Variables:
1743
- CHANNLIZE_API_KEY Alternative to --api-key
1744
- CHANNLIZE_PROJECT_ID Alternative to --project-id
1745
- CHANNLIZE_BASE_URL Alternative to --base-url
1746
- CHANNLIZE_TIMEOUT Alternative to --timeout
1747
-
1748
- Examples:
1749
- # Using command line arguments
1750
- channlize-mcp --api-key wk_123abc --project-id proj_456def
1751
-
1752
- # Using environment variables
1753
- export CHANNLIZE_API_KEY=wk_123abc
1754
- export CHANNLIZE_PROJECT_ID=proj_456def
1755
- channlize-mcp
1241
+ --base-url <url> API URL (default https://api.channlize.com)
1242
+ CHANNLIZE_BASE_URL
1243
+ --timeout <ms> Request timeout (default 10000)
1244
+ CHANNLIZE_TIMEOUT
1245
+ --transport <t> stdio (default) or http CHANNLIZE_MCP_TRANSPORT
1246
+ --host <host> HTTP bind address (default 127.0.0.1)
1247
+ --port <port> HTTP port (default 3333) CHANNLIZE_MCP_PORT
1248
+ -v, --version Print the version
1249
+ -h, --help Show this help
1250
+
1251
+ The server API key enables the inbox, contact, Events and log tools (read
1252
+ scope for reads, write scope to send). The widget key adds visitor tools
1253
+ (contact form, chat). Create keys in the Channlize dashboard under
1254
+ SDK & Integration \u2192 API keys.
1255
+
1256
+ Claude Code:
1257
+ claude mcp add channlize --env CHANNLIZE_SERVER_API_KEY=ch_live_\u2026 -- npx -y @channlize/mcp
1258
+
1259
+ Docs: https://github.com/channlize/sdk/tree/main/packages/mcp
1260
+ `;
1261
+ function fail(message) {
1262
+ console.error(
1263
+ `channlize-mcp: ${message}
1756
1264
 
1757
- Configuration in MCP Clients (e.g., Claude Desktop):
1758
- {
1759
- "mcpServers": {
1760
- "channlize": {
1761
- "command": "npx",
1762
- "args": [
1763
- "@channlize/mcp",
1764
- "--api-key",
1765
- "your-api-key",
1766
- "--project-id",
1767
- "your-project-id"
1768
- ]
1769
- }
1770
- }
1265
+ Run 'channlize-mcp --help' for usage.`
1266
+ );
1267
+ process.exit(1);
1268
+ }
1269
+ async function main() {
1270
+ let values;
1271
+ try {
1272
+ ({ values } = (0, import_node_util.parseArgs)({
1273
+ options: {
1274
+ "server-api-key": { type: "string" },
1275
+ "widget-key": { type: "string" },
1276
+ "api-key": { type: "string" },
1277
+ "project-id": { type: "string" },
1278
+ "base-url": { type: "string" },
1279
+ timeout: { type: "string" },
1280
+ transport: { type: "string" },
1281
+ host: { type: "string" },
1282
+ port: { type: "string" },
1283
+ help: { type: "boolean", short: "h" },
1284
+ version: { type: "boolean", short: "v" }
1285
+ },
1286
+ strict: true
1287
+ }));
1288
+ } catch (err) {
1289
+ fail(err.message);
1771
1290
  }
1772
-
1773
- Or with environment variables:
1774
- {
1775
- "mcpServers": {
1776
- "channlize": {
1777
- "command": "npx",
1778
- "args": ["@channlize/mcp"],
1779
- "env": {
1780
- "CHANNLIZE_API_KEY": "your-api-key",
1781
- "CHANNLIZE_PROJECT_ID": "your-project-id"
1782
- }
1783
- }
1291
+ if (values.help) {
1292
+ console.log(HELP);
1293
+ return;
1294
+ }
1295
+ if (values.version) {
1296
+ console.log(VERSION);
1297
+ return;
1298
+ }
1299
+ const str = (k) => values[k];
1300
+ const env = configFromEnv();
1301
+ const timeout = str("timeout");
1302
+ const config = {
1303
+ serverApiKey: str("server-api-key") ?? env.serverApiKey,
1304
+ widgetKey: str("widget-key") ?? env.widgetKey,
1305
+ apiKey: str("api-key") ?? env.apiKey,
1306
+ projectId: str("project-id") ?? env.projectId,
1307
+ baseUrl: str("base-url") ?? env.baseUrl,
1308
+ timeout: timeout !== void 0 ? Number(timeout) : env.timeout
1309
+ };
1310
+ const transport = str("transport") ?? process.env.CHANNLIZE_MCP_TRANSPORT ?? "stdio";
1311
+ if (transport !== "stdio" && transport !== "http") {
1312
+ fail(`unknown transport "${transport}": use stdio or http`);
1313
+ }
1314
+ let server;
1315
+ try {
1316
+ server = new ChannlizeMCPServer({ config });
1317
+ } catch (err) {
1318
+ if (err instanceof ConfigError) fail(err.message);
1319
+ throw err;
1320
+ }
1321
+ if (transport === "http") {
1322
+ const port = Number(str("port") ?? process.env.CHANNLIZE_MCP_PORT ?? 3333);
1323
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
1324
+ fail(`invalid port "${str("port") ?? process.env.CHANNLIZE_MCP_PORT}"`);
1784
1325
  }
1326
+ const { url: url2, close } = await server.runHttp({ host: str("host"), port });
1327
+ console.error(`Channlize MCP server listening on ${url2}`);
1328
+ const stop2 = async () => {
1329
+ await close();
1330
+ process.exit(0);
1331
+ };
1332
+ process.on("SIGINT", stop2);
1333
+ process.on("SIGTERM", stop2);
1334
+ return;
1785
1335
  }
1786
-
1787
- Resources:
1788
- channlize://schema/contact-form - Contact form JSON schema
1789
- channlize://schema/chat-session - Chat session JSON schema
1790
- channlize://docs/api-reference - Full API documentation
1791
- channlize://docs/quick-start - Quick start guide
1792
- channlize://docs/react-components - React components guide
1793
-
1794
- Tools:
1795
- submit_contact_form - Submit a contact form inquiry
1796
- create_chat_session - Create a new chat session
1797
- send_message - Send a message in a chat session
1798
- get_chat_messages - Retrieve chat message history
1799
- close_chat_session - Close an active chat session
1800
-
1801
- Prompts:
1802
- integrate-contact-form - Guide for adding contact form to React app
1803
- setup-live-chat - Guide for implementing live chat widget
1804
- customize-styling - Guide for theming components
1805
- handle-webhooks - Guide for webhook integration
1806
-
1807
- For more information, visit: https://github.com/channlize/sdk
1808
- `);
1809
- process.exit(0);
1810
- }
1811
- var config = {
1812
- apiKey: getArg("api-key") || process.env.CHANNLIZE_API_KEY || "",
1813
- projectId: getArg("project-id") || process.env.CHANNLIZE_PROJECT_ID,
1814
- baseUrl: getArg("base-url") || process.env.CHANNLIZE_BASE_URL,
1815
- timeout: parseInt(
1816
- getArg("timeout") || process.env.CHANNLIZE_TIMEOUT || "10000",
1817
- 10
1818
- )
1819
- };
1820
- if (!config.apiKey) {
1821
- console.error(`
1822
- Error: Channlize API key is required
1823
-
1824
- Please provide your API key using either:
1825
- --api-key <key>
1826
- or set CHANNLIZE_API_KEY environment variable
1827
-
1828
- Get your API key from your Channlize dashboard.
1829
-
1830
- Run 'channlize-mcp --help' for more information.
1831
- `);
1832
- process.exit(1);
1336
+ const stop = async () => {
1337
+ await server.close();
1338
+ process.exit(0);
1339
+ };
1340
+ process.on("SIGINT", stop);
1341
+ process.on("SIGTERM", stop);
1342
+ await server.runStdio();
1833
1343
  }
1834
- var server = new ChannlizeMCPServer({
1835
- name: "channlize-mcp",
1836
- version: "0.1.0",
1837
- config
1838
- });
1839
- process.on("SIGINT", async () => {
1840
- await server.close();
1841
- process.exit(0);
1842
- });
1843
- process.on("SIGTERM", async () => {
1844
- await server.close();
1845
- process.exit(0);
1846
- });
1847
- server.runStdio().catch((error) => {
1848
- console.error("Failed to start Channlize MCP server:", error);
1344
+ main().catch((err) => {
1345
+ console.error("Failed to start the Channlize MCP server:", err.message);
1849
1346
  process.exit(1);
1850
1347
  });
1851
1348
  //# sourceMappingURL=server.js.map