@channlize/mcp 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,1721 +1,1204 @@
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";
12
-
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"
2
+ import { McpServer } from "@modelcontextprotocol/server";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
4
+
5
+ // src/api.ts
6
+ import { ChannlizeClient, ChannlizeError } from "@channlize/core";
7
+ var ToolError = class extends Error {
8
+ constructor(message, statusCode, code) {
9
+ super(message);
10
+ this.statusCode = statusCode;
11
+ this.code = code;
12
+ this.name = "ToolError";
13
+ }
14
+ statusCode;
15
+ code;
16
+ };
17
+ function hintFor(status, apiMessage) {
18
+ switch (status) {
19
+ case 401:
20
+ 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.`;
21
+ case 403:
22
+ 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;
23
+ case 404:
24
+ return `${apiMessage}. Check the ID (and CHANNLIZE_PROJECT_ID): it must belong to the key's organization.`;
25
+ case 429:
26
+ return `${apiMessage}. Rate limited: wait a little before retrying.`;
27
+ default:
28
+ return apiMessage;
29
+ }
30
+ }
31
+ function toToolError(err) {
32
+ if (err instanceof ToolError) return err;
33
+ if (err instanceof ChannlizeError) {
34
+ return new ToolError(
35
+ err.statusCode ? hintFor(err.statusCode, err.message) : err.message,
36
+ err.statusCode,
37
+ err.code
38
+ );
39
+ }
40
+ return new ToolError(err instanceof Error ? err.message : String(err));
41
+ }
42
+ var ChannlizeApi = class {
43
+ constructor(config) {
44
+ this.config = config;
45
+ }
46
+ config;
47
+ projectIdPromise;
48
+ widgetClient;
49
+ get hasServerKey() {
50
+ return !!this.config.serverApiKey;
51
+ }
52
+ get hasWidgetKey() {
53
+ return !!this.config.widgetKey;
54
+ }
55
+ requireServerKey() {
56
+ if (!this.config.serverApiKey) {
57
+ throw new ToolError(
58
+ "This tool needs a server API key (ch_live_\u2026): set CHANNLIZE_SERVER_API_KEY or pass --server-api-key."
59
+ );
51
60
  }
61
+ return this.config.serverApiKey;
52
62
  }
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"
63
+ async request(method, path, options = {}) {
64
+ const key = this.requireServerKey();
65
+ const url2 = new URL(`${this.config.baseUrl}${path}`);
66
+ for (const [k, v] of Object.entries(options.query ?? {})) {
67
+ if (v !== void 0 && v !== "") url2.searchParams.set(k, String(v));
88
68
  }
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
- }
69
+ let res;
70
+ try {
71
+ res = await fetch(url2, {
72
+ method,
73
+ headers: {
74
+ Authorization: `Bearer ${key}`,
75
+ Accept: "application/json",
76
+ ...options.body !== void 0 ? { "Content-Type": "application/json" } : {},
77
+ ...options.headers
105
78
  },
106
- "attachments": {
107
- "type": "array",
108
- "items": { "$ref": "#/definitions/FileAttachment" }
109
- }
79
+ body: options.body !== void 0 ? JSON.stringify(options.body) : void 0,
80
+ signal: AbortSignal.timeout(this.config.timeout)
81
+ });
82
+ } catch (err) {
83
+ const e = err;
84
+ if (e.name === "TimeoutError" || e.name === "AbortError") {
85
+ throw new ToolError(
86
+ `Channlize API timed out after ${this.config.timeout} ms (${method} ${url2.pathname}). Retry, or raise CHANNLIZE_TIMEOUT.`,
87
+ 408,
88
+ "TIMEOUT"
89
+ );
110
90
  }
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" }
91
+ throw new ToolError(
92
+ `Could not reach the Channlize API at ${this.config.baseUrl}: ${e.message}`
93
+ );
94
+ }
95
+ const text = await res.text();
96
+ let data = void 0;
97
+ if (text) {
98
+ try {
99
+ data = JSON.parse(text);
100
+ } catch {
101
+ data = void 0;
120
102
  }
121
103
  }
104
+ if (!res.ok) {
105
+ const body = data ?? {};
106
+ const raw = Array.isArray(body.message) ? body.message.join(", ") : body.message;
107
+ const apiMessage = raw || (typeof body.error === "string" && /\s/.test(body.error) ? body.error : "") || `HTTP ${res.status} ${res.statusText}`.trim();
108
+ throw new ToolError(
109
+ hintFor(res.status, apiMessage.replace(/\.$/, "")),
110
+ res.status,
111
+ body.code ?? (typeof body.error === "string" ? body.error : void 0)
112
+ );
113
+ }
114
+ if (data === void 0) {
115
+ throw new ToolError(
116
+ `Unexpected non-JSON response from ${method} ${url2.pathname}`
117
+ );
118
+ }
119
+ return data;
122
120
  }
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.
121
+ /**
122
+ * `CHANNLIZE_PROJECT_ID`, or the first project of the key's organization
123
+ * (`GET /projects/me`), looked up once.
124
+ */
125
+ projectId() {
126
+ if (this.config.projectId) return Promise.resolve(this.config.projectId);
127
+ this.projectIdPromise ??= this.request(
128
+ "GET",
129
+ "/projects/me"
130
+ ).then((r) => {
131
+ if (!r.project?.id) {
132
+ throw new ToolError(
133
+ "Could not find a project for this key: set CHANNLIZE_PROJECT_ID."
134
+ );
135
+ }
136
+ return r.project.id;
137
+ }).catch((err) => {
138
+ this.projectIdPromise = void 0;
139
+ throw err;
140
+ });
141
+ return this.projectIdPromise;
142
+ }
143
+ /** `@channlize/core` client bound to the widget key (visitor tools). */
144
+ widget() {
145
+ if (!this.config.widgetKey) {
146
+ throw new ToolError(
147
+ "This tool needs the public widget key (wk_\u2026): set CHANNLIZE_WIDGET_KEY or pass --widget-key."
148
+ );
149
+ }
150
+ this.widgetClient ??= new ChannlizeClient({
151
+ apiKey: this.config.widgetKey,
152
+ projectId: this.config.projectId ?? "",
153
+ baseUrl: this.config.baseUrl,
154
+ timeout: this.config.timeout
155
+ });
156
+ return this.widgetClient;
157
+ }
158
+ /** `@channlize/core` client for `notify` (server key + project). */
159
+ async notifier() {
160
+ const serverApiKey = this.requireServerKey();
161
+ return new ChannlizeClient({
162
+ // core requires a widget key; notify only uses serverApiKey.
163
+ apiKey: this.config.widgetKey ?? "unused",
164
+ serverApiKey,
165
+ projectId: await this.projectId(),
166
+ baseUrl: this.config.baseUrl,
167
+ timeout: this.config.timeout
168
+ });
169
+ }
170
+ };
140
171
 
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..."
172
+ // src/config.ts
173
+ var DEFAULT_BASE_URL = "https://api.channlize.com";
174
+ var DEFAULT_TIMEOUT = 1e4;
175
+ var ConfigError = class extends Error {
176
+ constructor(message) {
177
+ super(message);
178
+ this.name = "ConfigError";
179
+ }
180
+ };
181
+ function keyKind(key) {
182
+ return key.startsWith("wk_") ? "widget" : "server";
151
183
  }
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."
184
+ function clean(value) {
185
+ const v = value?.trim();
186
+ return v ? v : void 0;
160
187
  }
161
- \`\`\`
162
-
163
- ### Chat Sessions
164
-
165
- **POST /v1/chat/sessions**
166
- Create a new chat session.
167
-
168
- Request Body:
169
- \`\`\`json
170
- {
171
- "metadata": {
172
- "pageUrl": "https://example.com",
173
- "userAgent": "Mozilla/5.0...",
174
- "customField": "value"
188
+ function resolveConfig(config) {
189
+ let serverApiKey = clean(config.serverApiKey);
190
+ let widgetKey = clean(config.widgetKey);
191
+ const apiKey = clean(config.apiKey);
192
+ if (serverApiKey && keyKind(serverApiKey) === "widget") {
193
+ throw new ConfigError(
194
+ "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."
195
+ );
175
196
  }
176
- }
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"
197
+ if (widgetKey && keyKind(widgetKey) === "server") {
198
+ throw new ConfigError(
199
+ "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_."
200
+ );
201
+ }
202
+ if (apiKey) {
203
+ if (keyKind(apiKey) === "widget") widgetKey ??= apiKey;
204
+ else serverApiKey ??= apiKey;
205
+ }
206
+ if (!serverApiKey && !widgetKey) {
207
+ throw new ConfigError(
208
+ "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."
209
+ );
210
+ }
211
+ const timeout = config.timeout ?? DEFAULT_TIMEOUT;
212
+ if (!Number.isFinite(timeout) || timeout <= 0) {
213
+ throw new ConfigError(
214
+ `Invalid timeout "${config.timeout}": use milliseconds, e.g. 10000`
215
+ );
216
+ }
217
+ const baseUrl = (clean(config.baseUrl) ?? DEFAULT_BASE_URL).replace(
218
+ /\/+$/,
219
+ ""
220
+ );
221
+ try {
222
+ new URL(baseUrl);
223
+ } catch {
224
+ throw new ConfigError(
225
+ `Invalid base URL "${config.baseUrl}": use e.g. ${DEFAULT_BASE_URL}`
226
+ );
190
227
  }
228
+ return {
229
+ serverApiKey,
230
+ widgetKey,
231
+ projectId: clean(config.projectId),
232
+ baseUrl,
233
+ timeout
234
+ };
235
+ }
236
+ function configFromEnv(env = process.env) {
237
+ const timeout = clean(env.CHANNLIZE_TIMEOUT);
238
+ return {
239
+ serverApiKey: clean(env.CHANNLIZE_SERVER_API_KEY),
240
+ widgetKey: clean(env.CHANNLIZE_WIDGET_KEY),
241
+ apiKey: clean(env.CHANNLIZE_API_KEY),
242
+ projectId: clean(env.CHANNLIZE_PROJECT_ID),
243
+ baseUrl: clean(env.CHANNLIZE_BASE_URL),
244
+ timeout: timeout ? Number(timeout) : void 0
245
+ };
191
246
  }
192
- \`\`\`
193
247
 
194
- **POST /v1/chat/sessions/:sessionId/messages**
195
- Send a message in a chat session.
248
+ // src/prompts.ts
249
+ import * as z from "zod/v4";
196
250
 
197
- Request: FormData with 'content' field and optional file attachments
251
+ // src/docs.ts
252
+ var QUICK_START = `# Channlize quick start
198
253
 
199
- **GET /v1/chat/sessions/:sessionId/messages**
200
- Get messages for a session. Query params: limit, offset
254
+ Channlize routes website conversations and app events to Slack.
201
255
 
202
- **POST /v1/chat/sessions/:sessionId/close**
203
- Close a chat session.
256
+ ## Keys
257
+ - **Widget key** (\`wk_\u2026\`): public, safe in browser code. Used by the chat widget,
258
+ contact form and browser log tracking (\`apiKey\` in the SDK config).
259
+ - **Server API key** (\`ch_live_\u2026\`): secret, server only (\`serverApiKey\`). Scopes:
260
+ \`read\` (conversations, contacts, logs, Events) and \`write\` (notify, server logs,
261
+ create conversations/contacts). Create both under SDK & Integration \u2192 API keys.
262
+ - **Project ID**: shown next to each widget key.
204
263
 
205
- ## Error Responses
264
+ ## Install
265
+ \`\`\`bash
266
+ npm install @channlize/core # any JS runtime
267
+ npm install @channlize/react # React components and hooks
268
+ \`\`\`
206
269
 
207
- Status codes:
208
- - 400: Validation error
209
- - 401: Authentication failed
210
- - 429: Rate limit exceeded
211
- - 500: Server error
270
+ ## Browser
271
+ \`\`\`ts
272
+ import { ChannlizeClient } from '@channlize/core';
212
273
 
213
- Error format:
214
- \`\`\`json
215
- {
216
- "error": "Error message",
217
- "code": "ERROR_CODE",
218
- "field": "fieldName" // for validation errors
219
- }
274
+ const client = new ChannlizeClient({ apiKey: 'wk_\u2026', projectId: '<project-id>' });
275
+ await client.submitContactForm({ name: 'Ada', email: 'ada@example.com', message: 'Hi!' });
276
+ await client.trackEvent('checkout.started', { properties: { cartId: 'c_1' } });
220
277
  \`\`\`
221
- `;
222
- var QUICK_START = `# Channlize Quick Start
223
278
 
224
- ## Installation
279
+ ## Server
280
+ \`\`\`ts
281
+ import { createNotifier } from '@channlize/core';
282
+
283
+ // Reads CHANNLIZE_SERVER_API_KEY (or CHANNLIZE_API_KEY) and CHANNLIZE_PROJECT_ID.
284
+ const notify = createNotifier();
285
+ await notify('user.subscribed', { username: 'ada', plan: 'Pro' });
286
+ \`\`\`
287
+
288
+ Errors are \`ChannlizeError\` (\`statusCode\`, \`code\`), with subclasses
289
+ \`ValidationError\` (400), \`AuthenticationError\` (401) and \`RateLimitError\` (429).
290
+ `;
291
+ var REACT_COMPONENTS = `# @channlize/react
225
292
 
226
293
  \`\`\`bash
227
- # For React applications
228
294
  npm install @channlize/react
229
-
230
- # For non-React applications
231
- npm install @channlize/core
232
295
  \`\`\`
233
296
 
234
- ## React Setup
235
-
236
- 1. Wrap your app with ChannlizeProvider:
297
+ ## Floating chat widget (all-in-one)
298
+ \`ChannlizeChat\` includes the provider, launcher button and chat window.
237
299
 
238
300
  \`\`\`tsx
239
- import { ChannlizeProvider } from '@channlize/react';
301
+ 'use client';
302
+ import { ChannlizeChat } from '@channlize/react';
240
303
 
241
- function App() {
304
+ export function SupportWidget() {
242
305
  return (
243
- <ChannlizeProvider
306
+ <ChannlizeChat
244
307
  config={{
245
- apiKey: 'your-widget-key',
246
- projectId: 'your-project-id',
308
+ apiKey: process.env.NEXT_PUBLIC_CHANNLIZE_WIDGET_KEY!, // wk_\u2026, public
309
+ projectId: process.env.NEXT_PUBLIC_CHANNLIZE_PROJECT_ID!,
247
310
  }}
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"
311
+ position="bottom-right"
312
+ primaryColor="#2563eb"
313
+ theme="auto"
314
+ chatProps={{ title: 'Acme Support', greeting: 'Hi there!', welcomeMessage: 'Ask us anything.' }}
265
315
  />
266
316
  );
267
317
  }
268
318
  \`\`\`
269
319
 
270
- 3. Add live chat:
320
+ Key props: \`position\` ('bottom-right' | 'bottom-left' | 'top-right' | 'top-left'),
321
+ \`primaryColor\`, \`theme\` ('auto' | 'light' | 'dark'), \`defaultOpen\`, \`showOnMobile\`,
322
+ \`offset\`, \`zIndex\`, \`onOpen\`, \`onClose\`, and \`chatProps\` (SupportChat props).
271
323
 
324
+ ## Provider + inline components
272
325
  \`\`\`tsx
273
- import { Chat } from '@channlize/react';
326
+ import { ChannlizeProvider, SupportChat, ContactForm } from '@channlize/react';
274
327
 
275
- function SupportPage() {
276
- return (
277
- <Chat
278
- autoStart={true}
279
- title="Customer Support"
280
- placeholder="How can we help?"
281
- />
282
- );
283
- }
328
+ <ChannlizeProvider config={{ apiKey: 'wk_\u2026', projectId: '<project-id>' }}>
329
+ <SupportChat title="Need help?" greeting="Hi!" suggestions={['Pricing', 'Billing']} />
330
+ <ContactForm submitText="Send" successMessage="Thanks! We'll reply by email." />
331
+ </ChannlizeProvider>
284
332
  \`\`\`
285
333
 
286
- ## Core SDK Usage
334
+ SupportChat props include \`title\`, \`greeting\`, \`welcomeMessage\`, \`suggestions\`,
335
+ \`placeholder\`, \`allowAttachments\`, \`allowEndChat\`, \`theme\`, \`primaryColor\`,
336
+ \`locale\`, \`maxLength\`, \`labels\` and the \`useChatSession\` options
337
+ (\`autoConnect\`, \`metadata\`, \`persistSession\`, \`onMessage\`, \`onError\`).
338
+
339
+ ContactForm props: \`customFields\`, \`onSuccess\`, \`onError\`, \`className\`, \`styles\`,
340
+ \`submitText\`, \`successMessage\`.
341
+
342
+ ## Hooks (inside ChannlizeProvider)
343
+ - \`useChatSession(options)\` \u2192 \`{ session, messages, isConnecting, isConnected, error,
344
+ sendMessage, startSession, endSession, reconnect }\`
345
+ - \`useContactForm({ onSuccess, onError })\` \u2192 \`{ submit, isSubmitting, isSuccess, error, reset }\`
346
+ - \`useChannlizeAnalytics()\` \u2192 \`{ track(name, { properties, severity }) }\` (log tracking)
347
+ - \`useChannlize()\` \u2192 \`{ client, config }\`
348
+
349
+ ## Next.js App Router
350
+ Components use state and effects: render them from a \`'use client'\` file. The widget
351
+ key is public (\`NEXT_PUBLIC_\u2026\` is fine); never put the server API key in a
352
+ \`NEXT_PUBLIC_\` variable.
353
+ `;
354
+ var EVENTS = `# Events: notify Slack from code
287
355
 
288
- For non-React applications:
356
+ Each event key (e.g. \`user.subscribed\`) has a Slack channel and a message template
357
+ with \`{{field}}\` placeholders, set under Events in the dashboard. A key that isn't set
358
+ up yet is saved as a disabled draft the first time it is sent (\`status: 'skipped'\`),
359
+ so you can ship code first and configure it after. Server only: needs a server API key
360
+ with the \`write\` scope.
289
361
 
290
- \`\`\`typescript
291
- import { ChannlizeClient } from '@channlize/core';
362
+ \`\`\`ts
363
+ import { createNotifier } from '@channlize/core';
292
364
 
293
- const client = new ChannlizeClient({
294
- apiKey: 'your-widget-key',
295
- projectId: 'your-project-id',
296
- });
365
+ type AppEvents = {
366
+ 'user.subscribed': { username: string; plan: string };
367
+ 'invoice.paid': { amount: string; customer: string };
368
+ };
297
369
 
298
- // Submit contact form
299
- await client.submitContactForm({
300
- name: 'John Doe',
301
- email: 'john@example.com',
302
- message: 'I need help!',
303
- });
370
+ // Env: CHANNLIZE_SERVER_API_KEY (or CHANNLIZE_API_KEY), CHANNLIZE_PROJECT_ID.
371
+ const notify = createNotifier<AppEvents>();
304
372
 
305
- // Create chat session
306
- const { session } = await client.createChatSession();
373
+ const res = await notify('user.subscribed', { username: 'ada', plan: 'Pro' });
374
+ // res.status: 'queued' | 'skipped' (see res.reason); res.duplicate on a repeated key
307
375
 
308
- // Send message
309
- await client.sendMessage(session.id, 'Hello!');
376
+ // Retry-safe: an idempotency key already used posts nothing.
377
+ await notify('invoice.paid', { amount: '$49', customer: 'ada@example.com' }, { idempotencyKey: 'inv_123' });
310
378
  \`\`\`
311
379
 
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.
380
+ To type keys app-wide instead, augment the interface:
381
+ \`\`\`ts
382
+ declare module '@channlize/core' {
383
+ interface ChannlizeEvents {
384
+ 'user.subscribed': { username: string; plan: string };
385
+ }
386
+ }
387
+ \`\`\`
324
388
 
325
- \`\`\`tsx
326
- <ChannlizeProvider config={{ apiKey, projectId }}>
327
- {children}
328
- </ChannlizeProvider>
389
+ Over HTTP:
390
+ \`\`\`bash
391
+ curl -X POST https://api.channlize.com/v1/notify \\
392
+ -H "Authorization: Bearer $CHANNLIZE_SERVER_API_KEY" \\
393
+ -H "Content-Type: application/json" \\
394
+ -H "Idempotency-Key: signup-42" \\
395
+ -d '{"projectId":"<project-id>","event":"user.subscribed","data":{"username":"ada"}}'
329
396
  \`\`\`
330
397
 
331
- ## Form
398
+ Log tracking is separate: \`client.trackEvent\` (browser) / \`trackServerEvent\` (server)
399
+ record log lines shown under Log tracking.
400
+ `;
332
401
 
333
- A complete contact form with validation and submission handling.
402
+ // src/prompts.ts
403
+ function user(text) {
404
+ return {
405
+ messages: [
406
+ { role: "user", content: { type: "text", text } }
407
+ ]
408
+ };
409
+ }
410
+ function registerServerPrompts(server) {
411
+ server.registerPrompt(
412
+ "triage-conversations",
413
+ {
414
+ title: "Triage open conversations",
415
+ description: "Review open support conversations, rank them by urgency and draft a reply for each.",
416
+ argsSchema: z.object({
417
+ status: z.string().optional().describe(
418
+ "Status to triage: open (default), pending, resolved or closed"
419
+ ),
420
+ limit: z.string().optional().describe("How many conversations (default 10)")
421
+ })
422
+ },
423
+ ({ status, limit }) => user(`Triage my Channlize support inbox.
334
424
 
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
425
+ 1. Call list_conversations with status "${status || "open"}" and limit ${Number(limit) || 10}.
426
+ 2. For each conversation, call get_conversation to read the thread.
427
+ 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.
428
+ 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
429
 
344
- ### Example
345
- \`\`\`tsx
346
- <Form
347
- customFields={[
430
+ 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.`)
431
+ );
432
+ server.registerPrompt(
433
+ "summarize-conversation",
348
434
  {
349
- name: 'department',
350
- label: 'Department',
351
- type: 'select',
352
- options: ['Sales', 'Support'],
353
- required: true,
435
+ title: "Summarize a conversation",
436
+ description: "Summarize one conversation and suggest the next step.",
437
+ argsSchema: z.object({
438
+ conversationId: z.string().describe("Conversation ID")
439
+ })
354
440
  },
355
- ]}
356
- onSuccess={() => alert('Sent!')}
357
- styles={{
358
- button: { background: '#007bff', color: 'white' },
359
- }}
360
- />
361
- \`\`\`
441
+ ({ conversationId }) => user(
442
+ `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.`
443
+ )
444
+ );
445
+ server.registerPrompt(
446
+ "check-events",
447
+ {
448
+ title: "Check Events delivery",
449
+ description: "Check that Events reach Slack: list them and explain failed or skipped deliveries.",
450
+ argsSchema: z.object({})
451
+ },
452
+ () => user(`Check my Channlize Events setup.
362
453
 
363
- ## Chat
454
+ 1. Call list_event_definitions and list the Events, flagging disabled drafts and Events without a Slack channel.
455
+ 2. Call list_event_deliveries (limit 50) and group the deliveries by status.
456
+ 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).`)
457
+ );
458
+ }
459
+ function registerGuidePrompts(server) {
460
+ server.registerPrompt(
461
+ "setup-events",
462
+ {
463
+ title: "Add Events to my app",
464
+ description: "Add Channlize Events (notify Slack from server code) to the current codebase.",
465
+ argsSchema: z.object({
466
+ events: z.string().optional().describe('Events to add, e.g. "signup, subscription upgrade"')
467
+ })
468
+ },
469
+ ({ events }) => user(`Add Channlize Events to this codebase${events ? ` for: ${events}` : ""}.
364
470
 
365
- Real-time chat widget with message history and file attachments.
471
+ 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
472
 
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
473
+ Reference:
377
474
 
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
- \`\`\`
475
+ ${EVENTS}`)
476
+ );
477
+ server.registerPrompt(
478
+ "setup-live-chat",
479
+ {
480
+ title: "Add the chat widget",
481
+ description: "Add the Channlize live chat widget to a React or Next.js app.",
482
+ argsSchema: z.object({
483
+ framework: z.string().optional().describe("e.g. Next.js App Router, Vite, Remix")
484
+ })
485
+ },
486
+ ({ 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
487
 
392
- ## Hooks
488
+ Reference:
393
489
 
394
- ### useContactForm()
395
- Hook for managing contact form state.
490
+ ${REACT_COMPONENTS}`)
491
+ );
492
+ server.registerPrompt(
493
+ "integrate-contact-form",
494
+ {
495
+ title: "Add a contact form",
496
+ description: "Add a Channlize contact form page to a React app.",
497
+ argsSchema: z.object({
498
+ styling: z.string().optional().describe("Styling approach, e.g. Tailwind or CSS modules")
499
+ })
500
+ },
501
+ ({ 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
502
 
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
- \`\`\`
503
+ Reference:
403
504
 
404
- ### useChatSession()
405
- Hook for managing chat sessions.
505
+ ${REACT_COMPONENTS}`)
506
+ );
507
+ }
406
508
 
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),
509
+ // src/resources.ts
510
+ var markdown = (uri, text) => ({
511
+ contents: [{ uri, mimeType: "text/markdown", text }]
421
512
  });
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
513
+ function registerResources(server, api) {
514
+ server.registerResource(
515
+ "quick-start",
516
+ "channlize://docs/quick-start",
517
+ {
518
+ title: "Channlize quick start",
519
+ description: "Keys, install, and first calls from the browser and the server.",
520
+ mimeType: "text/markdown"
521
+ },
522
+ (uri) => markdown(uri.href, QUICK_START)
523
+ );
524
+ server.registerResource(
525
+ "react-components",
526
+ "channlize://docs/react-components",
527
+ {
528
+ title: "@channlize/react components and hooks",
529
+ description: "ChannlizeChat, SupportChat, ContactForm, hooks and Next.js notes.",
530
+ mimeType: "text/markdown"
531
+ },
532
+ (uri) => markdown(uri.href, REACT_COMPONENTS)
533
+ );
534
+ server.registerResource(
535
+ "events",
536
+ "channlize://docs/events",
537
+ {
538
+ title: "Events: notify Slack from code",
539
+ description: "createNotifier / notify with typed events, idempotency and curl.",
540
+ mimeType: "text/markdown"
541
+ },
542
+ (uri) => markdown(uri.href, EVENTS)
543
+ );
544
+ if (api.hasServerKey) {
545
+ server.registerResource(
546
+ "project",
547
+ "channlize://project",
548
+ {
549
+ title: "Current Channlize project",
550
+ description: "The key's project: ID, name, totals, permissions and Slack connection.",
551
+ mimeType: "application/json"
552
+ },
553
+ async (uri) => {
554
+ const r = await api.request(
555
+ "GET",
556
+ "/projects/me"
557
+ );
558
+ return {
559
+ contents: [
560
+ {
561
+ uri: uri.href,
562
+ mimeType: "application/json",
563
+ text: JSON.stringify(r.project, null, 2)
564
+ }
565
+ ]
566
+ };
567
+ }
568
+ );
474
569
  }
475
- ];
476
- function getResource(uri) {
477
- return resources.find((r) => r.uri === uri);
478
- }
479
- function listResources() {
480
- return resources;
481
570
  }
482
571
 
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
- });
572
+ // src/tools.ts
573
+ import * as z2 from "zod/v4";
574
+ var STATUSES = ["open", "pending", "resolved", "closed"];
575
+ var PRIORITIES = ["low", "normal", "high", "urgent"];
576
+ var SEVERITIES = ["info", "warning", "error"];
577
+ var EVENT_KEY = /^[a-z0-9][a-z0-9._-]*$/;
578
+ function ok(data) {
579
+ return {
580
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
581
+ structuredContent: data
582
+ };
492
583
  }
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"]
584
+ async function run(fn) {
585
+ try {
586
+ return ok(await fn());
587
+ } catch (err) {
588
+ const e = toToolError(err);
589
+ return {
590
+ content: [{ type: "text", text: e.message }],
591
+ isError: true
592
+ };
593
+ }
594
+ }
595
+ var record2 = z2.record(z2.string(), z2.unknown());
596
+ var loose = z2.looseObject({});
597
+ var page = {
598
+ total: z2.number(),
599
+ limit: z2.number(),
600
+ offset: z2.number(),
601
+ hasMore: z2.boolean()
602
+ };
603
+ var conversationSummary = z2.looseObject({
604
+ id: z2.string(),
605
+ subject: z2.string().nullable().optional(),
606
+ status: z2.string(),
607
+ priority: z2.string().nullable().optional(),
608
+ customerEmail: z2.string().nullable().optional(),
609
+ customerName: z2.string().nullable().optional(),
610
+ tags: z2.array(z2.string()).nullable().optional(),
611
+ createdAt: z2.string().optional()
612
+ });
613
+ var READ = {
614
+ readOnlyHint: true,
615
+ destructiveHint: false,
616
+ idempotentHint: true,
617
+ openWorldHint: false
618
+ };
619
+ function registerServerTools(server, api) {
620
+ server.registerTool(
621
+ "get_project",
622
+ {
623
+ title: "Get project",
624
+ 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.",
625
+ inputSchema: z2.object({}),
626
+ outputSchema: z2.object({ project: loose }),
627
+ annotations: READ
531
628
  },
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
- });
629
+ () => run(async () => {
630
+ const r = await api.request("GET", "/projects/me");
631
+ return { project: r.project };
632
+ })
633
+ );
634
+ server.registerTool(
635
+ "get_analytics",
636
+ {
637
+ title: "Get analytics",
638
+ description: "Daily support activity for the last 30 days (UTC): conversations created and resolved, customer and agent messages.",
639
+ inputSchema: z2.object({}),
640
+ outputSchema: z2.object({ analytics: loose }),
641
+ annotations: READ
642
+ },
643
+ () => run(async () => {
644
+ const r = await api.request(
645
+ "GET",
646
+ "/projects/analytics"
647
+ );
648
+ return { analytics: r.analytics };
649
+ })
650
+ );
651
+ server.registerTool(
652
+ "list_conversations",
653
+ {
654
+ title: "List conversations",
655
+ 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.",
656
+ inputSchema: z2.object({
657
+ status: z2.enum(STATUSES).optional().describe("Only conversations with this status"),
658
+ priority: z2.enum(PRIORITIES).optional().describe("Only conversations with this priority"),
659
+ limit: z2.number().int().min(1).max(50).default(20).describe("How many to return (1-50, default 20)")
660
+ }),
661
+ outputSchema: z2.object({
662
+ conversations: z2.array(conversationSummary),
663
+ returned: z2.number(),
664
+ matched: z2.number(),
665
+ scanned: z2.number()
666
+ }),
667
+ annotations: READ
668
+ },
669
+ ({ status, priority, limit }) => run(async () => {
670
+ const r = await api.request(
671
+ "GET",
672
+ "/conversations"
673
+ );
674
+ const all = r.conversations ?? [];
675
+ const matched = all.filter(
676
+ (c) => (!status || c.status === status) && (!priority || c.priority === priority)
677
+ );
678
+ const conversations = matched.slice(0, limit);
543
679
  return {
544
- success: response.success ?? true,
545
- conversationId: response.conversationId,
546
- message: response.message || "Contact form submitted successfully"
680
+ conversations,
681
+ returned: conversations.length,
682
+ matched: matched.length,
683
+ scanned: all.length
547
684
  };
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
- }
685
+ })
686
+ );
687
+ server.registerTool(
688
+ "get_conversation",
689
+ {
690
+ title: "Get conversation",
691
+ description: "Read one conversation with its full message thread (customer, agent and system messages), status, priority and tags.",
692
+ inputSchema: z2.object({
693
+ conversationId: z2.string().uuid().describe("Conversation ID (from list_conversations)")
694
+ }),
695
+ outputSchema: z2.object({ conversation: loose }),
696
+ annotations: READ
697
+ },
698
+ ({ conversationId }) => run(async () => {
699
+ const conversation = await api.request(
700
+ "GET",
701
+ `/conversations/${encodeURIComponent(conversationId)}`
702
+ );
703
+ return { conversation };
704
+ })
705
+ );
706
+ server.registerTool(
707
+ "create_conversation",
708
+ {
709
+ title: "Create conversation",
710
+ 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.",
711
+ inputSchema: z2.object({
712
+ customerEmail: z2.email().describe("Customer's email address"),
713
+ customerName: z2.string().max(255).optional().describe("Customer's name"),
714
+ subject: z2.string().min(1).max(500).describe("Subject line"),
715
+ message: z2.string().min(1).describe("First message, from the customer"),
716
+ channel: z2.string().optional().describe("Slack channel ID to post to, overriding routing rules"),
717
+ tags: z2.array(z2.string()).max(20).optional().describe("Tags")
718
+ }),
719
+ outputSchema: z2.object({ conversation: loose }),
720
+ annotations: {
721
+ readOnlyHint: false,
722
+ destructiveHint: false,
723
+ idempotentHint: false,
724
+ openWorldHint: true
561
725
  }
562
726
  },
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"]
727
+ (args) => run(async () => {
728
+ const conversation = await api.request("POST", "/conversations", {
729
+ body: args
730
+ });
731
+ return { conversation };
732
+ })
733
+ );
734
+ server.registerTool(
735
+ "list_contacts",
736
+ {
737
+ title: "List contacts",
738
+ description: "List the customers who contacted the organization, with their conversation count. Paginate with limit/offset.",
739
+ inputSchema: z2.object({
740
+ limit: z2.number().int().min(1).max(100).default(50),
741
+ offset: z2.number().int().min(0).default(0)
742
+ }),
743
+ outputSchema: z2.object({ contacts: z2.array(loose), ...page }),
744
+ annotations: READ
595
745
  },
596
- handler: async (args, context) => {
597
- const client = createClient(context);
598
- const response = await client.sendMessage(args.sessionId, args.content);
746
+ ({ limit, offset }) => run(async () => {
747
+ const r = await api.request("GET", "/contacts", {
748
+ query: { limit, offset }
749
+ });
599
750
  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"
751
+ contacts: r.contacts ?? [],
752
+ total: Number(r.total ?? 0),
753
+ limit: Number(r.limit ?? limit),
754
+ offset: Number(r.offset ?? offset),
755
+ hasMore: Boolean(r.hasMore)
609
756
  };
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"]
757
+ })
758
+ );
759
+ server.registerTool(
760
+ "get_contact",
761
+ {
762
+ title: "Get contact",
763
+ description: "Read one contact (customer) by ID.",
764
+ inputSchema: z2.object({
765
+ contactId: z2.string().uuid().describe("Contact ID (from list_contacts)")
766
+ }),
767
+ outputSchema: z2.object({ contact: loose }),
768
+ annotations: READ
634
769
  },
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
770
+ ({ contactId }) => run(async () => {
771
+ const contact = await api.request(
772
+ "GET",
773
+ `/contacts/${encodeURIComponent(contactId)}`
641
774
  );
775
+ return { contact };
776
+ })
777
+ );
778
+ server.registerTool(
779
+ "create_contact",
780
+ {
781
+ title: "Create contact",
782
+ description: "Add a customer by email, or return the existing one with that email. Needs the write scope.",
783
+ inputSchema: z2.object({
784
+ email: z2.email(),
785
+ name: z2.string().max(255).optional(),
786
+ company: z2.string().optional(),
787
+ phone: z2.string().optional(),
788
+ tags: z2.array(z2.string()).max(20).optional()
789
+ }),
790
+ outputSchema: z2.object({ contact: loose }),
791
+ annotations: {
792
+ readOnlyHint: false,
793
+ destructiveHint: false,
794
+ idempotentHint: true,
795
+ openWorldHint: false
796
+ }
797
+ },
798
+ (args) => run(async () => {
799
+ const contact = await api.request("POST", "/contacts", {
800
+ body: args
801
+ });
802
+ return { contact };
803
+ })
804
+ );
805
+ server.registerTool(
806
+ "notify_event",
807
+ {
808
+ title: "Send an Event to Slack",
809
+ 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.',
810
+ inputSchema: z2.object({
811
+ event: z2.string().max(100).regex(
812
+ EVENT_KEY,
813
+ "lowercase letters, digits, dots, underscores or dashes"
814
+ ).describe("Event key, e.g. user.subscribed"),
815
+ data: record2.optional().describe(
816
+ 'Template fields, e.g. { "username": "ada", "plan": "Pro" }'
817
+ ),
818
+ idempotencyKey: z2.string().min(1).max(255).optional().describe("Send at most once per key (retry-safe)"),
819
+ channel: z2.string().optional().describe("Slack channel ID instead of the event's channel")
820
+ }),
821
+ outputSchema: z2.looseObject({
822
+ id: z2.string(),
823
+ event: z2.string(),
824
+ status: z2.string(),
825
+ reason: z2.string().optional(),
826
+ duplicate: z2.boolean().optional()
827
+ }),
828
+ annotations: {
829
+ readOnlyHint: false,
830
+ destructiveHint: false,
831
+ idempotentHint: false,
832
+ openWorldHint: true
833
+ }
834
+ },
835
+ ({ event, data, idempotencyKey, channel }) => run(async () => {
836
+ const client = await api.notifier();
837
+ const r = await client.notify(event, data, { idempotencyKey, channel });
642
838
  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
- }))
839
+ id: r.id,
840
+ event: r.event,
841
+ status: r.status,
842
+ ...r.reason ? { reason: r.reason } : {},
843
+ ...r.duplicate ? { duplicate: true } : {}
654
844
  };
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"]
845
+ })
846
+ );
847
+ server.registerTool(
848
+ "list_event_definitions",
849
+ {
850
+ title: "List Events",
851
+ 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.",
852
+ inputSchema: z2.object({}),
853
+ outputSchema: z2.object({
854
+ projectId: z2.string(),
855
+ definitions: z2.array(loose)
856
+ }),
857
+ annotations: READ
858
+ },
859
+ () => run(async () => {
860
+ const projectId = await api.projectId();
861
+ const r = await api.request(
862
+ "GET",
863
+ `/v1/projects/${encodeURIComponent(projectId)}/event-definitions`
864
+ );
865
+ return { projectId, definitions: r.definitions ?? [] };
866
+ })
867
+ );
868
+ server.registerTool(
869
+ "list_event_deliveries",
870
+ {
871
+ title: "List Event deliveries",
872
+ 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.",
873
+ inputSchema: z2.object({
874
+ definitionId: z2.string().uuid().optional().describe("Only this Event (ID from list_event_definitions)"),
875
+ limit: z2.number().int().min(1).max(100).default(20)
876
+ }),
877
+ outputSchema: z2.object({
878
+ projectId: z2.string(),
879
+ deliveries: z2.array(loose)
880
+ }),
881
+ annotations: READ
882
+ },
883
+ ({ definitionId, limit }) => run(async () => {
884
+ const projectId = await api.projectId();
885
+ const r = await api.request(
886
+ "GET",
887
+ `/v1/projects/${encodeURIComponent(projectId)}/event-deliveries`,
888
+ { query: { definitionId, limit } }
889
+ );
890
+ return { projectId, deliveries: r.deliveries ?? [] };
891
+ })
892
+ );
893
+ server.registerTool(
894
+ "query_logs",
895
+ {
896
+ title: "Query logs",
897
+ 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.",
898
+ inputSchema: z2.object({
899
+ severity: z2.enum(SEVERITIES).optional(),
900
+ source: z2.enum(["client_sdk", "server_api", "system"]).optional().describe(
901
+ "client_sdk = browser (widget key), server_api = server key"
902
+ ),
903
+ search: z2.string().max(200).optional().describe("Text to look for"),
904
+ from: z2.iso.datetime({ offset: true }).optional().describe("ISO 8601 start"),
905
+ to: z2.iso.datetime({ offset: true }).optional().describe("ISO 8601 end"),
906
+ projectId: z2.string().uuid().optional(),
907
+ limit: z2.number().int().min(1).max(100).default(50),
908
+ offset: z2.number().int().min(0).default(0)
909
+ }),
910
+ outputSchema: z2.object({ events: z2.array(loose), ...page }),
911
+ annotations: READ
669
912
  },
670
- handler: async (args, context) => {
671
- const client = createClient(context);
672
- const session = await client.closeChatSession(args.sessionId);
913
+ (args) => run(async () => {
914
+ const r = await api.request("GET", "/v1/events", { query: args });
673
915
  return {
674
- success: true,
675
- sessionId: session.id,
676
- status: session.status,
677
- closedAt: session.updatedAt,
678
- message: "Chat session closed successfully"
916
+ events: r.events ?? [],
917
+ total: Number(r.total ?? 0),
918
+ limit: Number(r.limit ?? args.limit),
919
+ offset: Number(r.offset ?? args.offset),
920
+ hasMore: Boolean(r.hasMore)
679
921
  };
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>
736
- );
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>
922
+ })
781
923
  );
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
924
+ server.registerTool(
925
+ "track_log",
926
+ {
927
+ title: "Record a log",
928
+ 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.",
929
+ inputSchema: z2.object({
930
+ name: z2.string().min(1).max(255).describe("Log name, e.g. payment.failed"),
931
+ severity: z2.enum(SEVERITIES).default("info"),
932
+ properties: record2.optional().describe("Extra fields")
933
+ }),
934
+ outputSchema: z2.object({ id: z2.string(), projectId: z2.string() }),
935
+ annotations: {
936
+ readOnlyHint: false,
937
+ destructiveHint: false,
938
+ idempotentHint: false,
939
+ openWorldHint: false
844
940
  }
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
941
  },
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
- }
942
+ ({ name, severity, properties }) => run(async () => {
943
+ const projectId = await api.projectId();
944
+ const r = await api.request("POST", "/v1/events", {
945
+ body: { projectId, name, severity, properties }
946
+ });
947
+ return { id: r.id, projectId };
948
+ })
949
+ );
1220
950
  }
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',
951
+ var VISITOR = {
952
+ readOnlyHint: false,
953
+ destructiveHint: false,
954
+ idempotentHint: false,
955
+ openWorldHint: true
956
+ };
957
+ function registerWidgetTools(server, api) {
958
+ server.registerTool(
959
+ "submit_contact_form",
960
+ {
961
+ title: "Submit contact form",
962
+ 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.",
963
+ inputSchema: z2.object({
964
+ name: z2.string().min(1).describe("Visitor's name"),
965
+ email: z2.email().describe("Visitor's email"),
966
+ subject: z2.string().optional(),
967
+ message: z2.string().min(1),
968
+ pageUrl: z2.url().optional().describe("Page the form was sent from")
969
+ }),
970
+ outputSchema: z2.looseObject({
971
+ conversationId: z2.string().optional(),
972
+ message: z2.string().optional()
973
+ }),
974
+ annotations: VISITOR
1242
975
  },
1243
- messageUser: {
1244
- background: '#3b82f6',
1245
- color: 'white',
1246
- padding: '0.75rem',
1247
- borderRadius: '12px 12px 4px 12px',
1248
- maxWidth: '70%',
1249
- marginLeft: 'auto',
976
+ (args) => run(async () => {
977
+ const r = await api.widget().submitContactForm(args);
978
+ return { conversationId: r.conversationId, message: r.message };
979
+ })
980
+ );
981
+ server.registerTool(
982
+ "create_chat_session",
983
+ {
984
+ title: "Start chat session",
985
+ description: "Start a live chat session as a website visitor (widget key). Returns the session ID for send_message.",
986
+ inputSchema: z2.object({
987
+ metadata: record2.optional().describe("Metadata to attach (e.g. user info)")
988
+ }),
989
+ outputSchema: z2.looseObject({
990
+ sessionId: z2.string(),
991
+ status: z2.string()
992
+ }),
993
+ annotations: VISITOR
1250
994
  },
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',
995
+ ({ metadata }) => run(async () => {
996
+ const r = await api.widget().createChatSession(metadata);
997
+ return {
998
+ sessionId: r.session.id,
999
+ status: r.session.status,
1000
+ createdAt: String(r.session.createdAt)
1001
+ };
1002
+ })
1003
+ );
1004
+ server.registerTool(
1005
+ "send_message",
1006
+ {
1007
+ title: "Send chat message",
1008
+ description: "Send a visitor message in a chat session (widget key); it is posted to the Slack thread.",
1009
+ inputSchema: z2.object({
1010
+ sessionId: z2.string().min(1).describe("From create_chat_session"),
1011
+ content: z2.string().min(1).max(5e3)
1012
+ }),
1013
+ outputSchema: z2.looseObject({
1014
+ messageId: z2.string(),
1015
+ sessionStatus: z2.string()
1016
+ }),
1017
+ annotations: VISITOR
1258
1018
  },
1259
- input: {
1260
- border: '1px solid #d1d5db',
1261
- borderRadius: '6px',
1262
- padding: '0.75rem',
1263
- fontSize: '1rem',
1019
+ ({ sessionId, content }) => run(async () => {
1020
+ const r = await api.widget().sendMessage(sessionId, content);
1021
+ return { messageId: r.message.id, sessionStatus: r.session.status };
1022
+ })
1023
+ );
1024
+ server.registerTool(
1025
+ "get_chat_messages",
1026
+ {
1027
+ title: "Read chat messages",
1028
+ description: "Read a chat session's messages, including agent replies from Slack (widget key).",
1029
+ inputSchema: z2.object({
1030
+ sessionId: z2.string().min(1),
1031
+ limit: z2.number().int().min(1).max(100).default(50),
1032
+ offset: z2.number().int().min(0).default(0)
1033
+ }),
1034
+ outputSchema: z2.object({
1035
+ sessionId: z2.string(),
1036
+ messages: z2.array(loose)
1037
+ }),
1038
+ annotations: { ...READ, openWorldHint: true }
1264
1039
  },
1265
- button: {
1266
- background: '#3b82f6',
1267
- color: 'white',
1268
- padding: '0.75rem 1.5rem',
1269
- borderRadius: '6px',
1270
- border: 'none',
1271
- cursor: 'pointer',
1040
+ ({ sessionId, limit, offset }) => run(async () => {
1041
+ const messages = await api.widget().getChatMessages(sessionId, limit, offset);
1042
+ return {
1043
+ sessionId,
1044
+ messages: messages.map((m) => ({
1045
+ id: m.id,
1046
+ author: m.author,
1047
+ authorName: m.authorData?.name,
1048
+ content: m.content,
1049
+ timestamp: String(m.timestamp),
1050
+ attachments: m.attachments?.length ?? 0
1051
+ }))
1052
+ };
1053
+ })
1054
+ );
1055
+ server.registerTool(
1056
+ "close_chat_session",
1057
+ {
1058
+ title: "End chat session",
1059
+ description: "End a chat session as the visitor (widget key).",
1060
+ inputSchema: z2.object({ sessionId: z2.string().min(1) }),
1061
+ outputSchema: z2.looseObject({
1062
+ sessionId: z2.string(),
1063
+ status: z2.string()
1064
+ }),
1065
+ annotations: { ...VISITOR, idempotentHint: true }
1272
1066
  },
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)
1067
+ ({ sessionId }) => run(async () => {
1068
+ const s = await api.widget().closeChatSession(sessionId);
1069
+ return { sessionId: s.id ?? sessionId, status: s.status };
1070
+ })
1358
1071
  );
1359
1072
  }
1360
1073
 
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');
1074
+ // src/http.ts
1075
+ import { createServer } from "http";
1076
+ import {
1077
+ createMcpHandler,
1078
+ hostHeaderValidationResponse,
1079
+ localhostAllowedHostnames,
1080
+ localhostAllowedOrigins,
1081
+ originValidationResponse
1082
+ } from "@modelcontextprotocol/server";
1083
+ var LOCAL_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1"]);
1084
+ async function toRequest(req, origin) {
1085
+ const headers = new Headers();
1086
+ for (const [k, v] of Object.entries(req.headers)) {
1087
+ if (Array.isArray(v)) v.forEach((x) => headers.append(k, x));
1088
+ else if (v !== void 0) headers.set(k, v);
1424
1089
  }
1425
-
1426
- // Example: Sentiment analysis
1427
- const sentiment = await analyzeSentiment(message);
1428
- if (sentiment === 'negative') {
1429
- await escalateConversation(data.conversation.id, 'high');
1090
+ let body;
1091
+ if (req.method !== "GET" && req.method !== "HEAD") {
1092
+ const chunks = [];
1093
+ for await (const chunk of req) chunks.push(chunk);
1094
+ body = new Uint8Array(Buffer.concat(chunks));
1430
1095
  }
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,
1096
+ const controller = new AbortController();
1097
+ req.on("close", () => {
1098
+ if (!req.complete) controller.abort();
1439
1099
  });
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,
1100
+ return new Request(new URL(req.url ?? "/", origin), {
1101
+ method: req.method,
1102
+ headers,
1103
+ body,
1104
+ signal: controller.signal
1447
1105
  });
1448
1106
  }
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"
1107
+ async function startHttpServer(factory, options = {}) {
1108
+ const host = options.host ?? "127.0.0.1";
1109
+ const port = options.port ?? 3333;
1110
+ const path = options.path ?? "/mcp";
1111
+ const local = LOCAL_HOSTS.has(host);
1112
+ const handler = createMcpHandler(() => factory(), {
1113
+ onerror: (err) => console.error("[channlize-mcp]", err.message)
1114
+ });
1115
+ const server = createServer(async (req, res) => {
1116
+ try {
1117
+ const request = await toRequest(
1118
+ req,
1119
+ `http://${req.headers.host ?? `${host}:${port}`}`
1120
+ );
1121
+ const url2 = new URL(request.url);
1122
+ if (url2.pathname !== path) {
1123
+ res.writeHead(404, { "Content-Type": "text/plain" }).end(`Not found: use ${path}`);
1124
+ return;
1125
+ }
1126
+ const rejected = local ? hostHeaderValidationResponse(request, localhostAllowedHostnames()) ?? originValidationResponse(request, localhostAllowedOrigins()) : void 0;
1127
+ const response = rejected ?? await handler.fetch(request);
1128
+ res.writeHead(response.status, Object.fromEntries(response.headers));
1129
+ if (response.body) {
1130
+ for await (const chunk of response.body) {
1131
+ res.write(chunk);
1132
+ }
1133
+ }
1134
+ res.end();
1135
+ } catch (err) {
1136
+ console.error("[channlize-mcp]", err.message);
1137
+ if (!res.headersSent)
1138
+ res.writeHead(500, { "Content-Type": "text/plain" });
1139
+ res.end("Internal server error");
1530
1140
  }
1531
- }
1141
+ });
1142
+ await new Promise((resolve, reject) => {
1143
+ server.once("error", reject);
1144
+ server.listen(port, host, () => resolve());
1145
+ });
1146
+ const address = server.address();
1147
+ const actualPort = typeof address === "object" && address ? address.port : port;
1148
+ const shownHost = host.includes(":") ? `[${host}]` : host;
1149
+ return {
1150
+ url: `http://${shownHost}:${actualPort}${path}`,
1151
+ server,
1152
+ close: () => new Promise((r) => server.close(() => r()))
1153
+ };
1532
1154
  }
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
1155
 
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
- `
1156
+ // src/index.ts
1157
+ var VERSION = true ? "0.3.0" : "0.0.0";
1158
+ var INSTRUCTIONS = `Channlize routes website conversations and app events to the team's Slack.
1159
+ - Inbox: list_conversations / get_conversation, then contacts. Replies and status changes happen in Slack or the dashboard.
1160
+ - Events (notify_event) post a Slack message per event key; list_event_definitions and list_event_deliveries explain what was sent or skipped.
1161
+ - Logs: query_logs searches log tracking; track_log records one.
1162
+ - Write tools need a server API key with the write scope; a 403 means the key is read-only.`;
1163
+ function buildServer(config, info = {}) {
1164
+ const api = new ChannlizeApi(resolveConfig(config));
1165
+ const server = new McpServer(
1166
+ { name: info.name ?? "channlize", version: info.version ?? VERSION },
1167
+ { instructions: INSTRUCTIONS }
1168
+ );
1169
+ if (api.hasServerKey) {
1170
+ registerServerTools(server, api);
1171
+ registerServerPrompts(server);
1552
1172
  }
1553
- ];
1554
- function getPrompt(name) {
1555
- return prompts.find((p) => p.name === name);
1556
- }
1557
- function listPrompts() {
1558
- return prompts;
1173
+ if (api.hasWidgetKey) registerWidgetTools(server, api);
1174
+ registerResources(server, api);
1175
+ registerGuidePrompts(server);
1176
+ return server;
1559
1177
  }
1560
-
1561
- // src/index.ts
1562
1178
  var ChannlizeMCPServer = class {
1563
1179
  server;
1564
1180
  config;
1181
+ info;
1565
1182
  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();
1183
+ this.config = resolveConfig(options.config ?? {});
1184
+ this.info = { name: options.name, version: options.version };
1185
+ this.server = buildServer(this.config, this.info);
1581
1186
  }
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
- });
1696
- }
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
- */
1187
+ /** The underlying `McpServer`. */
1706
1188
  getServer() {
1707
1189
  return this.server;
1708
1190
  }
1709
- /**
1710
- * Start the server with stdio transport
1711
- */
1191
+ /** Serve over stdio (what desktop and IDE clients launch). */
1712
1192
  async runStdio() {
1713
- const transport = new StdioServerTransport();
1714
- await this.server.connect(transport);
1193
+ await this.server.connect(new StdioServerTransport());
1715
1194
  }
1716
1195
  /**
1717
- * Close the server
1196
+ * Serve Streamable HTTP at `http://<host>:<port>/mcp` (default
1197
+ * 127.0.0.1:3333). Each request gets a fresh server; nothing is shared.
1718
1198
  */
1199
+ async runHttp(options = {}) {
1200
+ return startHttpServer(() => buildServer(this.config, this.info), options);
1201
+ }
1719
1202
  async close() {
1720
1203
  await this.server.close();
1721
1204
  }
@@ -1726,13 +1209,14 @@ function createMCPServer(options) {
1726
1209
  var index_default = ChannlizeMCPServer;
1727
1210
  export {
1728
1211
  ChannlizeMCPServer,
1212
+ ConfigError,
1213
+ ToolError,
1214
+ VERSION,
1215
+ buildServer,
1216
+ configFromEnv,
1729
1217
  createMCPServer,
1730
1218
  index_default as default,
1731
- getPrompt,
1732
- getResource,
1733
- getTool,
1734
- listPrompts,
1735
- listResources,
1736
- listTools
1219
+ keyKind,
1220
+ resolveConfig
1737
1221
  };
1738
1222
  //# sourceMappingURL=index.mjs.map