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