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