@channlize/mcp 0.1.1 → 0.4.0

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