Reference

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.

EndpointPurpose
GET /widget.jsServe the bundle
GET /api/widget/config/:apiKeyTheme, position, locale
POST /api/widget/messageReceive a message
WS /api/widget/wsReal-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.

ToolScopePurpose
cradle_search_kbkb:readRAG search
cradle_list_kbskb:readList knowledge bases
cradle_list_agentsagents:readList agents
cradle_execute_agentagents:executeStateless agent call
cradle_get_tickettickets:readRead a ticket
cradle_list_ticketstickets:readList tickets
cradle_summarize_notesagents:executeClean and summarize notes
cradle_submit_issuemessages:writeCreate 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

  1. SmartNotes sends dictated notes to cradle_summarize_notes. Cradle cleans the text, summarizes it, and extracts issues, returning them to the client.
  2. 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.