Chat channels
How Telegram, the web widget, the HTTP API, and the MCP server plug into the same ChatChannel interface.
All Cradle inputs share a single ChatChannel abstraction. Whether a message
arrives from Telegram, a website widget, a REST call, or an MCP client, it passes
through the same triage, RAG, and approval pipeline.
ChatChannel interface
type IncomingMessage = {
channelId: 'telegram' | 'widget' | 'api'
chatId: string
userId?: string
text: string
timestamp: number
attachments?: Array<{ type: string; url?: string; name?: string }>
channelMeta: Record<string, unknown>
}
type SendMeta = { inReplyTo?: string; markdown?: boolean }
interface ChatChannel {
id: string
start(): Promise<void>
stop(): Promise<void>
onMessage(handler: (msg: IncomingMessage) => void): void
sendMessage(chatId: string, text: string, meta?: SendMeta): Promise<void>
sendAttachment?(chatId: string, attachment: unknown): Promise<void>
markTyping?(chatId: string): Promise<void>
}ChannelManager (src/core/channels/channel-manager.ts) keeps a registry of
channels, starts and stops them according to channels_config.enabled, and
routes outgoing replies back to the right channel.
Telegram
TelegramChannel is built on grammY with long polling. No
public URL is required, which fits on-premise deployments behind firewalls.
Configuration is just the bot token in
channels_config.telegram.config.botToken.
Web widget
WidgetChannel hosts a Fastify server in the main process on port 31415 by
default. It serves a self-contained IIFE bundle (packages/widget/) that mounts
via a shadow DOM and communicates over WebSocket.
Embed snippet:
<script
src="https://cradle.example.com:31415/widget.js"
data-cradle-widget
data-api-key="wk_..."
data-accent="#10b981"
data-position="bottom-right"
async></script>The bundle is ~49 KB gzipped. History is stored in browser localStorage, and
visitorId is generated on first open.
| Endpoint | Purpose |
|---|---|
GET /widget.js | Serve the bundle |
GET /api/widget/config/:apiKey | Theme, position, locale |
POST /api/widget/message | Receive a message |
WS /api/widget/ws | Real-time replies and typing |
Widget auth uses a channel-level API key and an optional allowedOrigins list.
HTTP API
ApiChannel is the REST channel described in
HTTP API reference. It supports sync, async, and webhook
modes and is intended for mobile apps, web backends, CLI integrations, and
automation tools.
MCP server
McpServerChannel exposes Cradle capabilities as MCP tools over Streamable HTTP
on port 31417 by default. This direction is the default: Cradle is the server,
and clients such as SmartNotes, n8n, or Claude Code call it.
| Tool | Scope | Purpose |
|---|---|---|
cradle_search_kb | kb:read | RAG search |
cradle_list_kbs | kb:read | List knowledge bases |
cradle_list_agents | agents:read | List agents |
cradle_execute_agent | agents:execute | Stateless agent call |
cradle_get_ticket | tickets:read | Read a ticket |
cradle_list_tickets | tickets:read | List tickets |
cradle_summarize_notes | agents:execute | Clean and summarize notes |
cradle_submit_issue | messages:write | Create a ticket in the shared Inbox |
MCP requests reuse the same Bearer / X-Api-Key middleware and scope checks as
the HTTP API before reaching the MCP transport.
SmartNotes flow
- SmartNotes sends dictated notes to
cradle_summarize_notes. Cradle cleans the text, summarizes it, and extracts issues, returning them to the client. - The operator reviews issues in SmartNotes and sends selected ones via
cradle_submit_issue. Cradle creates a ticket in the shared Inbox, runs triage across all projects, and routes the issue to the winning agent's project. There is no auto-reply; every submitted issue lands in operator review.
Starting and stopping channels
The desktop app shows each channel in Integrations. On a headless server,
channels boot automatically if enabled in channels_config. The CLI can list
channels with cradle channels list.