Munin
Munin developer portal
Get a key →
Section · MCP Tools

Tools your agent can call.

Munin exposes 159 tools at /mcp. Audiences gate which tokens see which tools — admin keys see everything, delegated end-user tokens see only self-service tools.

Connect a client

Wire your favourite model to Munin in a couple of minutes.

Admin tools 154

Available to admin API keys and admin sessions. Includes everything that writes or that reads across the org.

pingPing the MCP server
adminself-serviceread-only

Verify the MCP pipe; echoes a message and returns the resolved org and actor type.

Input schema
messagestringoptional
analytics_export_configAnalytics: Export trackers + visitor identities
adminread-only

Export this org's analytics configuration — trackers and visitor-identity links — as a portable JSON payload. Low-volume, returned in one shot. Tracker identity-verification secrets are redacted (the ciphertext is useless on another server); the operator re-enters them after import. Pair with `analytics_export_events` (paginated) and feed both into `analytics_import` on another Munin server.

analytics:read
Input schema
(no fields documented)
analytics_export_eventsAnalytics: Export view + search events (paginated)
adminread-only

Export this org's analytics events (page-view and search events) as a portable JSON payload. High-volume, so this is keyset-paginated over (createdAt, id): call with no arguments for the first page, then pass the returned `nextCursor` back as `cursor` until it comes back `null`. `limit` defaults to 200 (max 500). Feed each page's `records` into `analytics_import`. Import trackers + visitor identities first via `analytics_export_config` so event foreign keys resolve.

analytics:read
Input schema
cursorstringoptional
limitintegeroptional
analytics_importAnalytics: Import data
admindestructive

Import analytics `config` (trackers + visitor identities) and/or `events` (view + search events) produced by `analytics_export_config` / `analytics_export_events`, typically from another Munin server. Trackers are upserted by (org, name); visitor identities by (org, visitorId). Trackers import without their redacted identity-verification secret — rotate it afterwards. Visitor identities and events resolve their end-user / tracker foreign keys through `idMap`, so import end-users first and pass that `idMap` back in here. Events have no natural key: they are de-duplicated only within one run via `idMap` (re-running without the prior `idMap` inserts duplicates). Returns counts, warnings, and the merged `idMap` (source id → id on this server) — pass it forward to later imports.

analytics:write
Input schema
configobjectoptional
eventsobjectoptional
idMapobjectoptional
analytics_create_trackerAnalytics: Create tracker key
admindestructive

Create a tracker and mint a public `mn_track_*` API key bound to it. The key is safe to embed in `<script>` tags or mobile clients — it can only write page-view events scoped to your org, never read them. `allowedOrigins` is an optional list of full origins (`https://example.com`) the tracker will accept; when empty, any origin is accepted (set `MUNIN_TRACKER_REQUIRE_ALLOWLIST=1` to fail-closed instead). Returns the plaintext key once; store it where it needs to be embedded. Scaffolding a frontend from Lovable/Bolt/v0/Replit/Cursor? Read `skill://playbooks/frontend-integration` first — it covers the tracker + widget + CMS wiring end-to-end.

analytics:write
Input schema
namestringrequired
allowedOriginsstring[]optional
requireVerifiedIdentitybooleanoptional
analytics_list_trackersAnalytics: List tracker keys
adminread-only

List analytics trackers for the current org with their key prefix, allowed origins, and revocation state. Plaintext keys are never returned; rotate via `analytics_revoke_tracker` + `analytics_create_tracker`.

analytics:read
Input schema
includeRevokedbooleanoptional
analytics_update_trackerAnalytics: Update tracker config
admindestructive

Update a tracker's display name and/or `allowedOrigins`. The bound API key is unchanged — rotate via `analytics_revoke_tracker` + `analytics_create_tracker`.

analytics:write
Input schema
trackerIdstringrequired
namestringoptional
allowedOriginsstring[]optional
requireVerifiedIdentitybooleanoptional
analytics_rotate_tracker_identity_secretAnalytics: Rotate tracker identity verification secret
admindestructive

Mint a fresh HMAC secret for verifying visitor-identity claims sent to `/v1/a/identify`. Returns the plaintext secret once; store it server-side and use it to compute `userHash = HMAC_SHA256('${externalId}:${visitorId}', secret)` — the hash binds the visitor, so read `visitorId` from `window.mn.getVisitorId()` first — before calling `window.mn.identify(externalId, userHash)` from the browser. The previous secret is replaced immediately — any in-flight identify calls signed with it will fail.

analytics:write
Input schema
trackerIdstringrequired
analytics_rotate_tracker_keyAnalytics: Rotate tracker key
admindestructive

Revoke any active `mn_track_*` keys bound to this tracker and mint a fresh one. Returns the new plaintext key once; update any page that embeds the old key. Pages still embedding the old key will silently stop recording views once revocation lands.

analytics:write
Input schema
trackerIdstringrequired
analytics_list_top_subjectsAnalytics: Top subjects by view count
adminread-only

List the most-viewed subjects (CMS entries, landing pages, etc.) over a recent window. Use this to see what content is actually getting traffic. Filter by `subjectType` to scope to one surface (e.g. `cms_entry`). Pass `endUserId` or `contactId` to restrict the ranking to one identified visitor — useful for "what has this lead been reading?".

analytics:read
Input schema
subjectTypestringoptional
sinceDaysintegerrequired
limitintegerrequired
sourceenumoptional
endUserIdstringoptional
contactIdstringoptional
analytics_list_top_countriesAnalytics: Visitors by country
adminread-only

Visitor and view counts grouped by ISO 3166-1 alpha-2 country code over a recent window. Requires the backend to have `MUNIN_GEOIP_DB_PATH` configured; rows recorded without a GeoIP DB carry `country = NULL` and roll up into an "unknown" bucket. Filter by `subjectType` (e.g. `page`, `cms_entry`) or `source` to scope.

analytics:read
Input schema
subjectTypestringoptional
sinceDaysintegerrequired
limitintegerrequired
sourceenumoptional
analytics_get_traffic_by_sourceAnalytics: Traffic by UTM source
adminread-only

Views and unique visitors grouped by `utm_source` (with `utm_medium` / `utm_campaign` breakdown). Use this to compare campaign attribution: which channels actually drive engaged traffic vs. just clicks. Rows where `utm_source` is NULL (no campaign params on the URL) roll into a single "direct/organic" bucket.

analytics:read
Input schema
subjectTypestringoptional
sinceDaysintegerrequired
limitintegerrequired
sourceenumoptional
analytics_list_referrer_hostsAnalytics: Top referrer hosts
adminread-only

External traffic sources grouped by the host portion of `referrer`. Use this to see which sites are linking to you (HN, Reddit, partner blogs). Same-origin referrers are excluded server-side via the `excludeHost` argument (typically your own production host); pass it to keep internal navigations from drowning out external referrals. Rows with NULL referrer (direct navigation, bookmarks, link-with-`rel=noreferrer`) roll into a single "direct" bucket.

analytics:read
Input schema
subjectTypestringoptional
excludeHoststringoptional
sinceDaysintegerrequired
limitintegerrequired
sourceenumoptional
analytics_get_views_over_timeAnalytics: Daily view time-series
adminread-only

Daily view + unique-visitor counts over a recent window. Returns one row per UTC day, ordered oldest → newest, with zero-filled gaps so days with no traffic appear as `views: 0`. Use this to spot trends, weekly patterns, and the impact of campaigns or content launches.

analytics:read
Input schema
subjectTypestringoptional
subjectIdstringoptional
sinceDaysintegerrequired
sourceenumoptional
endUserIdstringoptional
contactIdstringoptional
analytics_get_subject_engagementAnalytics: Engagement for one subject
adminread-only

View counts, unique visitors, and average dwell/read-depth for one subject (e.g. one CMS entry) over a recent window. Use this when judging whether a stale entry should be refreshed or archived.

analytics:read
Input schema
subjectTypestringrequired
subjectIdstringrequired
sinceDaysintegerrequired
endUserIdstringoptional
contactIdstringoptional
analytics_get_funnelAnalytics: Conversion funnel across ordered steps
adminread-only

Compute a conversion funnel over page-view events: how many distinct visitors reached each ordered step, and where they drop off. Pass 2–8 `steps`; each step matches a view event by `subjectType` and/or `subjectId` (e.g. `{ subjectType: "page", subjectId: "/pricing" }`) and/or a `pathLike` SQL LIKE pattern (e.g. `{ pathLike: "/blog/%" }`). Steps are strictly ordered — a visitor counts at step N only if they hit step N after reaching step N-1. Visitors are grouped by a stable actor key (their identified end-user when known, else their anonymous `visitor_id`), so a journey that spans the anonymous → identified transition is not double-counted. Set `stepWindowHours` to require each step to follow the previous within a time budget (e.g. signup within 24h of viewing pricing). Anonymous funnels work without any identity setup. Returns per-step actor counts plus conversion/drop rates.

analytics:read
Input schema
stepsobject[]required
sinceDaysintegerrequired
stepWindowHoursintegeroptional
sourceenumoptional
analytics_get_contact_journeyAnalytics: Journey of subjects viewed by a contact
adminread-only

Chronological list of page-view and search events recorded for one identified visitor. Pass either `contactId` (resolved through `crm_contacts.endUserId`) or `endUserId` directly. Returns the ordered event timeline — what the lead looked at before they reached out, what they searched for, etc. Visitors are linked to an end-user identity by the chat-widget on first chat, or via `window.mn.identify(externalId, userHash)`. Events recorded under a `visitor_id` *before* that link was established are still included retroactively — the link is resolved at read time — so the journey spans the visitor's anonymous history too.

analytics:read
Input schema
contactIdstringoptional
endUserIdstringoptional
sinceDaysintegerrequired
limitintegerrequired
analytics_list_zero_result_searchesAnalytics: Zero-result search queries
adminread-only

List recent public search queries that returned zero results. The single best input for "what should we write about next" — readers are asking but Munin has no answer.

analytics:read
Input schema
subjectTypestringoptional
sinceDaysintegerrequired
limitintegerrequired
analytics_revoke_trackerAnalytics: Revoke tracker key
admindestructive

Revoke the API key bound to a tracker. After this, the key is rejected by the ingest endpoints — any pages still embedding it will silently fail to record views. The tracker row stays for audit.

analytics:write
Input schema
trackerIdstringrequired
cms_list_collectionsCMS: List collections
adminread-only

List CMS collections (content types) defined for your org. Scaffolding a frontend from Lovable/Bolt/v0/Replit/Cursor and need to render CMS content? Read `skill://playbooks/frontend-integration` — the delivery API is anonymous and intentionally has no CORS, so the fetch must run server-side.

cms:read
Input schema
(no fields documented)
cms_get_collectionCMS: Read collection
adminread-only

Read one collection by id or slug, including its field definitions.

cms:read
Input schema
idOrSlugstringrequired
cms_create_collectionCMS: Create collection
admindestructive

Define a new collection. Fields is an ORDERED array of { name, type, required?, options? } — field order is the render order in editor and public surfaces, so put fields in the sequence a human would read or fill them (e.g. lede asset → headline → excerpt → metadata → body → trailing/optional fields). See field types: text, rich_text, markdown, number, integer, boolean, date, datetime, select, multi_select, asset, reference, array, json.

cms:write
Input schema
namestringrequired
slugstringrequired
descriptionstringoptional
fieldsunknown[]required
localizedbooleanoptional
settingsobjectoptional
cms_update_collectionCMS: Update collection
admindestructive

Patch a collection. When supplied, `fields` REPLACES the existing array — so include every field you want to keep, in the order they should render (the array order is the render order). Field migration is lossy: dropped or renamed fields stay in entries' `data` jsonb but stop being read by the projection layer.

cms:write
Input schema
idOrSlugstringrequired
patchobjectrequired
cms_delete_collectionCMS: Delete collection
admindestructive

Delete a collection. Cascades to all entries, versions, and references.

cms:write
Input schema
idOrSlugstringrequired
cms_list_entriesCMS: List entries
adminread-only

List entries. Filters: collection (id or slug), status, locale. Drafts and scheduled entries are returned to admins; the public delivery API only ever returns published.

cms:read
Input schema
collectionstringoptional
statusenumoptional
localestringoptional
limitintegeroptional
includeenum[]optionalSet to ["references"] to expand reference fields into the referenced entries.
cms_get_entryCMS: Read entry
adminread-only

Read one entry. Data is projected through the collection's current field schema.

cms:read
Input schema
idstringrequired
includeenum[]optionalSet to ["references"] to expand reference fields into the referenced entries.
cms_create_entryCMS: Create entry
admindestructive

Create a new entry in a collection. `data` is keyed by field name; required fields must be present. Pass `status: "published"` to publish on creation; default is draft.

cms:write
Input schema
collectionstringrequired
slugstringrequired
localestringoptional
dataobjectrequired
statusenumoptional
cms_update_entryCMS: Update entry
admindestructive

Update an entry. Pass `ifVersion` (the current version you read) for optimistic concurrency. `data` is a partial patch — keys you send replace the corresponding keys on the existing entry; keys you omit are preserved. Pass an explicit `null` to clear a single key. The merged payload is then re-validated against the collection schema, and search_text + embedding + references are regenerated.

cms:write
Input schema
idstringrequired
ifVersionintegerrequired
slugstringoptional
localestringoptional
dataobjectoptional
cms_publish_entryCMS: Publish entry
admindestructive

Flip an entry to status="published". Stamps publishedAt and fires cms.entry.published.

cms:write
Input schema
idstringrequired
ifVersionintegerrequired
cms_unpublish_entryCMS: Unpublish entry
admindestructive

Revert an entry to status="draft". Clears publishedAt; fires cms.entry.unpublished.

cms:write
Input schema
idstringrequired
ifVersionintegerrequired
cms_schedule_publishCMS: Schedule entry publish
admindestructive

Schedule an entry to flip to published at a future ISO 8601 datetime. The schedule worker drains due rows every minute.

cms:write
Input schema
idstringrequired
ifVersionintegerrequired
scheduledAtstringrequired
cms_delete_entryCMS: Delete entry
admindestructive

Delete an entry. Cascades to its versions and references.

cms:write
Input schema
idstringrequired
ifVersionintegerrequired
cms_list_versionsCMS: List entry versions
adminread-only

List all prior versions of an entry, newest first.

cms:read
Input schema
entryIdstringrequired
cms_restore_versionCMS: Restore entry version
admindestructive

Roll an entry back to an earlier version. Creates a new current version with that historical data.

cms:write
Input schema
entryIdstringrequired
versionintegerrequired
ifVersionintegerrequired
cms_list_assetsCMS: List assets
adminread-only

List media-library assets in your org.

cms:read
Input schema
limitintegeroptional
cms_request_asset_uploadCMS: Request asset upload URL
admindestructive

Mint a presigned upload for a new asset. Only usable from clients that can issue raw HTTP PUT/POST themselves. If your runtime has no client-side PUT primitive, this tool is not a fit — use `cms_upload_asset_from_base64` (small inline base64) or `cms_upload_asset_from_url` (public HTTPS URL) instead. Flow: the asset row is created in `uploaded:false` state. Look at `uploadMethod`: if `"PUT"`, send the file body as the raw PUT body to `uploadUrl`. If `"POST"`, send multipart/form-data to `uploadUrl` including every key/value in `uploadFields` followed by a `file` part; the embedded policy enforces an exact `Content-Length` match. Then call `cms_complete_asset_upload` to verify and mark the row live. SVG uploads are rejected — SVG can carry inline scripts and is not safe to serve as an asset.

cms:write
Input schema
namestringrequired
mimestringrequired
sizeBytesintegerrequired
altTextstringoptional
metadataobjectoptional
cms_upload_asset_from_base64CMS: Upload asset from base64
admindestructive

Upload a small asset inline as base64 (≤100 KB decoded). The right choice when you have generated the asset in this conversation (image-gen output, screenshot, plot) and need it in the CMS without leaving the chat: compress to WebP or JPEG well under 100 KB first, then pass the bytes here. SVG is rejected. For larger assets reachable over HTTPS use `cms_upload_asset_from_url`; for larger arbitrary files use `cms_request_asset_upload` from a client that can issue HTTP PUT.

cms:write
Input schema
namestringrequired
mimestringrequired
base64Bodystringrequired
altTextstringoptional
metadataobjectoptional
cms_upload_asset_from_urlCMS: Upload asset from URL
admindestructive

Fetch a publicly reachable HTTPS asset and store it as a CMS asset in one call. Use this when your runtime cannot PUT to a presigned URL or pass large base64 payloads — typical for ChatGPT/Claude workspace agents whose sandbox blocks outbound PUTs and truncates long base64 strings. The server fetches the URL with SSRF protection, validates content-type (image/*, video/*, audio/*, application/pdf — SVG rejected) and size (≤50 MB), and creates the asset row in `uploaded:true` state. Filename and MIME are inferred from the response unless overridden. The original URL is recorded in `metadata.sourceUrl`.

cms:write
Input schema
sourceUrlstringrequired
namestringoptional
mimestringoptional
altTextstringoptional
metadataobjectoptional
cms_complete_asset_uploadCMS: Complete asset upload
admindestructive

Mark a previously-requested asset upload as complete.

cms:write
Input schema
idstringrequired
cms_delete_assetCMS: Delete asset
admindestructive

Delete an asset and remove the underlying file from storage. Fails with a conflict if the asset is still referenced by any entry, as a typed asset field or inline in a body.

cms:write
Input schema
idstringrequired
cms_list_localesCMS: List locales
adminread-only

List configured locales for your org. The default locale is used when an entry omits one.

cms:read
Input schema
(no fields documented)
cms_create_localeCMS: Create locale
admindestructive

Add a locale. Code is ISO 639-1 (e.g. "en") or BCP-47 ("en-US"). The first locale is the default unless overridden.

cms:write
Input schema
codestringrequired
namestringrequired
isDefaultbooleanoptional
cms_set_default_localeCMS: Set default locale
admindestructive

Set which locale is treated as the org's default for new entries.

cms:write
Input schema
codestringrequired
cms_list_inbound_referencesCMS: List inbound references
adminread-only

List entries that link to the given entry. Useful before deleting — see "what would break".

cms:read
Input schema
entryIdstringrequired
cms_list_asset_usageCMS: List asset usage
adminread-only

List entries that use the given asset — as a typed asset field, as an inline reference inside a markdown/rich_text body, or inside a block. Useful before deleting an asset — an asset that is still in use cannot be deleted.

cms:read
Input schema
assetIdstringrequired
cms_exportCMS: Export data
adminread-only

Export this org's CMS (locales, collections, entries, and assets) as a portable JSON payload. Pair with `cms_import` on another Munin server to move content between self-hosted and cloud. Asset bytes are included base64-encoded (assets larger than 5MB are exported as metadata only). Entry embeddings are not included — they are regenerated on import. Feed the returned `records` straight into `cms_import`.

cms:read
Input schema
(no fields documented)
cms_importCMS: Import data
admindestructive

Import CMS `records` produced by `cms_export` (typically from another Munin server). Locales are upserted by code, collections by slug, entries by (collection, slug, locale), and assets by (name, size) — so re-running is idempotent. Asset bytes are re-uploaded to this server and entry references/asset ids are rewritten to local ids. Entry embeddings are regenerated here. Returns counts and an `idMap` (source id → id on this server); pass that `idMap` back into later imports so dependent records resolve their parents.

cms:write
Input schema
recordsobjectrequired
idMapobjectoptional
conv_list_conversationsConv: List conversations
adminread-only

List conversations for your org, newest activity first. Filter by status (open / snoozed / closed / spam), assignee, or topic.

conv:read
Input schema
statusenumoptional
assigneeUserIdstringoptional
topicIdstringoptional
limitintegeroptional
conv_get_conversationConv: Read conversation
adminread-only

Read one conversation including every public + internal message.

conv:read
Input schema
idstringrequired
conv_send_messageConv: Send message in conversation
admindestructive

Append a message to a conversation. Pass `internal: true` to leave a staff-only note (drafts, side comments) — end-user agents never see internal messages.

conv:write
Input schema
conversationIdstringrequired
bodystringrequired
internalbooleanoptional
inReplyToIdstringoptional
conv_assign_conversationConv: Assign conversation
admindestructive

Assign a conversation to a user (pass user id) or unassign (pass null). Useful for routing escalated conversations.

conv:write
Input schema
idstringrequired
assigneeUserIdunknownrequired
conv_change_statusConv: Change conversation status
admindestructive

Change a conversation's status. `snoozeUntil` (ISO 8601) is required when status is "snoozed".

conv:write
Input schema
idstringrequired
statusenumrequired
snoozeUntilstringoptional
conv_request_handoverConv: Request handover to a human
admindestructive

Flag a conversation as needing human attention. Use this when you have reached the limit of what you can resolve autonomously — billing decisions, refunds outside policy, sensitive complaints, anything where a human teammate should step in. Appends an internal system note (visible only to staff) recording your stated `reason`, sets the conversation's "needs human attention" flag (which pins it to the top of the dashboard's Conversations page), and emits `conversation.handover_requested`. Pass `suggestedReply` ONLY when you have a substantive answer to propose — write it as the reply a teammate could send the end-user to resolve the issue, so they can edit, approve, or rewrite it. Don't fill it with a "a teammate will follow up" acknowledgement or a copy of any message already sent to the end-user; if you have no real answer to suggest, OMIT it. Idempotent — calling again on an already-flagged conversation is a no-op. The flag clears automatically once a human teammate replies or closes the conversation.

conv:write
Input schema
conversationIdstringrequired
reasonstringoptional
suggestedReplystringoptional
publicFallbackMessagestringoptional
conv_search_messagesConv: Search conversation messages
adminread-only

Substring search over message bodies. Returns the matching messages newest first; use conv_get_conversation to load surrounding context.

conv:read
Input schema
querystringrequired
limitintegeroptional
conv_list_channelsConv: List conversation channels
adminread-only

List conversation channels configured for your org. Currently shipping adapters: email and chat (widget). The `voice` and `sms` channel types are reserved for upcoming adapters.

conv:read
Input schema
(no fields documented)
conv_create_channelConv: Create conversation channel
admindestructive

Add a new conversation channel. Currently shipping adapters: `email` and `chat` (widget). Channel-specific configuration goes in `config`. The `voice` and `sms` channel types are reserved and not yet wired to an adapter.

conv:write
Input schema
typeenumrequired
vendorstringrequired
namestringrequired
configobjectoptional
conv_list_topicsConv: List conversation topics
adminread-only

List conversation topics (Billing, Support, Refunds, …) for your org.

conv:read
Input schema
(no fields documented)
conv_create_topicConv: Create conversation topic
admindestructive

Add a new conversation topic. Slug must be lowercase letters, digits, hyphens.

conv:write
Input schema
namestringrequired
slugstringrequired
colorstringoptional
conv_set_topicConv: Set or clear a conversation topic
admindestructive

Tag a conversation with one of the org's existing topics, or pass `topicId: null` to clear the topic. Use `conv_list_topics` first to see what topics exist; topics must be pre-created via `conv_create_topic`.

conv:write
Input schema
conversationIdstringrequired
topicIdunknownrequired
conv_set_subjectConv: Set or clear a conversation subject
admindestructive

Set a conversation's subject — the short human-readable title shown in the inbox and the chat widget — or pass `subject: null` to clear it. Used by the set-topic-and-title curator skill to title conversations that arrive without a subject (chat, SMS, voice). Email conversations already carry the email Subject line; don't overwrite it.

conv:write
Input schema
conversationIdstringrequired
subjectunknownrequired
conv_strip_message_signatureConv: Strip the signature from an inbound message
admindestructive

Replace an inbound message's body with a signature-stripped version. Used by the strip-email-signature curator skill — runs after the regex quote-stripper to clean up the trailing sign-off / contact block. The original body is kept in `metadata.preStripBody` for audit; the removed signature (if provided) is stored in `metadata.signatureText`. Refuses if the new body is empty or if the message isn't an end-user inbound in the caller's org. A cut that removes more than half the body is allowed only when `signatureText` is supplied, matches the removed trailing portion, and carries multiple contact-info hints (email, phone, address, URL) — this is what lets a one-line reply followed by a large contact block be cleaned.

conv:write
Input schema
messageIdstringrequired
bodystringrequired
signatureTextstringoptional
conv_exportConv: Export data
adminread-only

Export this org's conversation channels, conversations, and messages as a portable JSON payload. Pair with `conv_import` on another Munin server to move conversations between self-hosted and cloud. Channel credentials are NOT included — they are encrypted with this server's key and must be re-entered on the target. Feed the returned `records` straight into `conv_import`.

conv:read
Input schema
(no fields documented)
conv_importConv: Import data
admindestructive

Import conversation `records` produced by `conv_export` (typically from another Munin server). Channels are upserted by (type, vendor, name) and recreated without credentials — re-enter them on this server. Conversations and messages are append-only with no natural key: fresh ids are generated and parent FKs (channelId, conversationId) are resolved through the `idMap`. Returns counts, `warnings`, and an `idMap` (source id → id on this server); pass that `idMap` back into later imports so dependent records resolve their parents. Re-running within a single migration is idempotent via the idMap, but messages are not deduplicated across separate runs.

conv:write
Input schema
recordsobjectrequired
idMapobjectoptional
conv_setup_email_channelConv: Set up email channel
admindestructive

Create or update an email channel's transport configuration. Pass plaintext SMTP / IMAP passwords; the server encrypts them before storage and returns them redacted. Set `outbound.provider: 'mailer'` to send via Munin's configured Resend mailer instead of a custom SMTP host. Set `defaultAgentMode: 'draft_only'` on an outreach-only inbox so inbound replies are always drafted for human approval rather than auto-sent.

conv:write
Input schema
channelIdstringoptional
namestringrequired
configobjectrequired
defaultAgentModeenumoptional
conv_test_email_channelConv: Test email channel credentials
admindestructive

Test an email channel's stored credentials. Attempts an SMTP connect (and an IMAP connect if inbound is configured) without sending or fetching anything. Returns `{ smtp: "ok" | error, imap: "ok" | error | "not configured" }`.

conv:write
Input schema
channelIdstringrequired
conv_send_email_testConv: Send test email
admindestructive

Send a real test email through this channel's configured outbound transport (SMTP or Mailer). The message is addressed `to` the recipient you pass in. Useful for confirming credentials and deliverability end-to-end.

conv:write
Input schema
channelIdstringrequired
tostringrequired
conv_call_contactConv: Place a voice call to this conversation's contact
admindestructive

Place an outbound voice call to the contact attached to a conversation. Resolves the phone number from the conversation's contact. If your org has more than one active voice channel, pass `channelId` to pick one; with a single channel the call falls back to it. For arbitrary destinations, use `conv_call_channel` instead.

conv:write
Input schema
conversationIdstringrequired
channelIdstringoptional
conv_widget_create_channelConv: Create chat-widget channel
admindestructive

Create a chat-widget channel and mint a widget API key (`mn_widget_*`) bound to it. Returns the plaintext key once; store it server-side and pass it as `Authorization: Bearer` when calling POST /v1/widget/messages from the external agent. Scaffolding a frontend from Lovable/Bolt/v0/Replit/Cursor? Read `skill://playbooks/frontend-integration` first — it covers the widget + tracker + CMS wiring end-to-end.

conv:write
Input schema
namestringrequired
originAllowliststring[]required
webhookOnEscalationstringoptional
requireVerifiedIdentitybooleanoptional
conv_widget_update_channelConv: Update chat-widget channel
admindestructive

Update a chat-widget channel's originAllowlist / webhookOnEscalation. Pass null to clear webhookOnEscalation. The widget API key is unchanged.

conv:write
Input schema
channelIdstringrequired
originAllowliststring[]optional
webhookOnEscalationunknownoptional
requireVerifiedIdentitybooleanoptional
conv_widget_rotate_keyConv: Rotate widget API key
admindestructive

Revoke any active widget keys bound to this channel and mint a fresh `mn_widget_*` key. Returns the new plaintext key once. Existing inflight requests using the old key keep working until revocation lands.

conv:write
Input schema
channelIdstringrequired
conv_widget_rotate_identity_secretConv: Rotate widget identity-verification secret
admindestructive

Generate a fresh per-channel HMAC secret used to verify browser-side `data-user-hash` values against `data-external-id`. The previous secret is replaced atomically; any previously-issued user hashes stop verifying immediately and the operator must re-render their pages with newly-computed hashes. Returns the new plaintext once.

conv:write
Input schema
channelIdstringrequired
conv_list_channel_vendorsConv: List configurable channel vendors
adminread-only

List the voice/SMS channel vendors you can configure, with each vendor’s `kind`, capabilities (call/sendTest), and config fields (name, required, secret, description). Use this to discover what to pass to conv_configure_channel.

conv:read
Input schema
(no fields documented)
conv_list_channel_optionsConv: List a channel vendor’s selectable options
adminread-only

Discover the selectable options a vendor needs before you configure a channel — e.g. Threll workers, Vapi assistants — so you can pass a valid id to conv_configure_channel instead of guessing. Pass `vendor` + `config` (credentials) before the channel exists, or `channelId` for an existing channel. Returns option `groups` (e.g. `workers`, `assistants`), each with `{ value, label, hint }`.

conv:read
Input schema
vendorstringoptionalVendor to discover options for (with `config`). Omit when passing `channelId`.
channelIdstringoptionalDiscover options for an existing channel using its stored credentials.
configobjectoptionalVendor credentials to discover with before the channel exists (e.g. Threll `apiKey`+`accountId`, Vapi `apiKey`). Required with `vendor`.
conv_configure_channelConv: Configure a voice/SMS channel
admindestructive

Create or update a voice or SMS channel for any supported vendor. Pass `vendor` + a vendor-specific `config` object (see conv_list_channel_vendors). Pass `channelId` to update; omit to create. Plaintext secrets in `config` are encrypted before storage and returned redacted.

conv:write
Input schema
vendorstringrequiredChannel vendor, e.g. "vapi", "threll", "twilio", "messagebird". Call conv_list_channel_vendors for the full list and each vendor’s config fields.
channelIdstringoptionalPass an existing channel id to update; omit to create a new channel.
namestringoptionalChannel display name. Required on create.
configobjectrequiredVendor-specific configuration object. The exact fields (and which are secret) come from conv_list_channel_vendors. Plaintext secrets are encrypted before storage and returned redacted.
conv_test_channelConv: Test a channel’s stored credentials
admindestructive

Verify a channel’s stored credentials with its vendor (no message sent). The result shape is vendor-specific.

conv:write
Input schema
channelIdstringrequired
conv_call_channelConv: Place an outbound voice call
admindestructive

Place an outbound voice call through a voice channel (any vendor). The channel’s configured assistant/worker runs the conversation.

conv:write
Input schema
channelIdstringrequired
tostringrequired
customerNamestringoptional
conv_send_channel_testConv: Send a real test message
admindestructive

Send a real test message (e.g. SMS) through a channel that supports it, addressed to `to`. Useful for end-to-end deliverability checks.

conv:write
Input schema
channelIdstringrequired
tostringrequired
bodystringoptional
crm_list_contactsCRM: List contacts
adminread-only

List contacts in your org, newest-updated first. Filter by company or tag.

crm:read
Input schema
companyIdstringoptional
tagstringoptional
limitintegeroptional
crm_get_contactCRM: Read contact
adminread-only

Read one contact, including AI fields, tags, custom fields, and compliance flags.

crm:read
Input schema
idstringrequired
crm_find_contactCRM: Find contact by email or phone
adminread-only

Find an existing contact by email and/or phone before creating a new one. Returns null if no match.

crm:read
Input schema
emailstringoptional
phonestringoptional
crm_create_contactCRM: Create contact
admindestructive

Create a new contact. Search with crm_find_contact first to avoid duplicates.

crm:write
Input schema
namestringoptional
emailstringoptional
phonestringoptional
titlestringoptional
addressstringoptional
companyIdstringoptional
endUserIdstringoptional
tagsstring[]optional
customFieldsobjectoptional
crm_update_contactCRM: Update contact
admindestructive

Update fields on a contact. Only keys present in `patch` are touched; omitted keys are preserved. `customFields` is a partial patch — keys you send replace the corresponding keys; keys you omit are preserved (send `key: null` to clear a single custom field). Setting `doNotContact: true` also stamps `unsubscribedAt`; setting it false clears it. Pass `mode: 'fill-null'` from automated/curator contexts to refuse overwriting existing non-null values (only null/empty fields are filled). Default `mode: 'overwrite'` applies the patch as-is and is appropriate for human-driven edits.

crm:write
Input schema
idstringrequired
patchobjectrequired
modeenumoptionalWhen 'fill-null', only applies patch keys whose existing value on the contact is null or empty — non-null fields are left untouched. Default 'overwrite' applies the patch as-is. Curator skills that backfill automated data should pass 'fill-null'; human-driven dashboard edits should use the default.
crm_bulk_create_contactsCRM: Bulk-create contacts
admindestructive

Bulk-create contacts with dedupe + compliance checks: rows whose email or phone already match a do_not_contact contact are skipped, as are rows that would duplicate an existing contact.

crm:write
Input schema
contactsobject[]required
crm_search_contactsCRM: Search contacts
adminread-only

Substring search across name, email, phone, and title. Returns contacts ordered newest-updated first.

crm:read
Input schema
querystringrequired
limitintegeroptional
crm_list_companiesCRM: List companies
adminread-only

List companies in your org.

crm:read
Input schema
limitintegeroptional
crm_create_companyCRM: Create company
admindestructive

Create a new company.

crm:write
Input schema
namestringrequired
domainstringoptional
tagsstring[]optional
customFieldsobjectoptional
crm_list_pipelinesCRM: List sales pipelines
adminread-only

List sales pipelines for your org with their stages in position order.

crm:read
Input schema
(no fields documented)
crm_create_pipelineCRM: Create sales pipeline
admindestructive

Create a new sales pipeline with at least one stage. Stages are inserted in array order; mark a stage `winLoss: "won"` or `"lost"` to record terminal outcomes.

crm:write
Input schema
namestringrequired
slugstringrequired
stagesobject[]required
crm_list_dealsCRM: List deals
adminread-only

List deals, optionally filtered by pipeline or stage.

crm:read
Input schema
pipelineIdstringoptional
stageIdstringoptional
limitintegeroptional
crm_create_dealCRM: Create deal
admindestructive

Create a new deal in a pipeline. If `stageId` is omitted, the deal lands in the pipeline's first stage by position.

crm:write
Input schema
namestringrequired
pipelineIdstringrequired
stageIdstringoptional
amountCentsintegeroptional
currencystringoptional
primaryContactIdstringoptional
companyIdstringoptional
expectedCloseAtstringoptional
crm_change_stageCRM: Change deal stage
admindestructive

Move a deal to a new stage. If the destination stage is a won/lost terminal, `closedAt` is stamped automatically.

crm:write
Input schema
dealIdstringrequired
stageIdstringrequired
crm_log_activityCRM: Log activity
admindestructive

Record an activity (note / call / email / meeting / task) against a contact, company, or deal. If `contactId` is set, the contact's `lastContactedAt` is also bumped.

crm:write
Input schema
typeenumrequired
subjectstringoptional
bodystringoptional
contactIdstringoptional
companyIdstringoptional
dealIdstringoptional
dueAtstringoptional
completedAtstringoptional
metadataobjectoptional
crm_list_activitiesCRM: List activities
adminread-only

List CRM activities filtered by contact, deal, or company.

crm:read
Input schema
contactIdstringoptional
dealIdstringoptional
companyIdstringoptional
limitintegeroptional
crm_set_ai_summaryCRM: Set AI summary or next action
admindestructive

Set the AI-generated summary and/or next-action for a contact, company, or deal. These live in dedicated columns so agents do not pollute the human-edited description.

crm:write
Input schema
entityTypeenumrequired
idstringrequired
summaryunknownoptional
nextActionunknownoptional
crm_propose_mergeCRM: Propose a merge candidate
admindestructive

File a structured proposal that two contacts are the same person. Pass `confidence` ("high" | "medium"), `evidence` (the matched signals — same email, same phone, similar name, etc.), `recommendedKeeperId` (which row to keep), and optionally `recommendedPatch` (fields to copy onto the keeper from the duplicate). Idempotent on the (contactA, contactB) pair while a pending proposal exists — calling again upserts the existing pending row. The CRM clean-contact-data curator runs this on a periodic cadence; see `skill://crm/clean-contact-data`.

crm:write
Input schema
contactAIdstringrequired
contactBIdstringrequired
confidenceenumrequired
evidenceobjectrequired
recommendedKeeperIdstringrequired
recommendedPatchobjectoptional
crm_list_merge_proposalsCRM: List merge proposals
adminread-only

List CRM merge proposals, defaulting to `status: "pending"` (the operator review queue). Returns each proposal with both contacts embedded as summaries — no extra `crm_get_contact` calls needed. Pass `status: "dismissed"` once per curator pass to skip pairs the operator has already rejected.

crm:read
Input schema
statusenumoptional
limitintegeroptional
crm_apply_merge_proposalCRM: Apply a merge proposal
admindestructive

Atomically apply a pending merge proposal: copies `recommendedPatch` fields onto the keeper, archives the duplicate (adds `dedup-archived-YYYY-MM` tag, sets `customFields.mergedInto = <keeperId>`, sets `doNotContact: true`), and marks the proposal `applied`. Activities and deals stay on whichever contactId they were originally logged under — that's a documented v1 limitation. Throws if the proposal is not in `pending` status.

crm:write
Input schema
idstringrequired
crm_dismiss_merge_proposalCRM: Dismiss a merge proposal
admindestructive

Mark a pending merge proposal as dismissed (the operator decided these are not the same person). The next CRM hygiene curator pass queries dismissed proposals and skips refiling the same pair. Optional `reason` is stored for audit. Throws if the proposal is not in `pending` status.

crm:write
Input schema
idstringrequired
reasonstringoptional
crm_list_segmentsCRM: List segments
adminread-only

List saved contact segments. A segment is a named CRM filter — used as the audience for outreach campaigns and other targeting workflows. Returns each segment with its filter definition.

crm:read
Input schema
(no fields documented)
crm_get_segmentCRM: Read segment
adminread-only

Read one segment, including its filter definition.

crm:read
Input schema
idstringrequired
crm_create_segmentCRM: Create segment
admindestructive

Create a saved contact segment. `filter` supports tagsAny (any-of match), tagsAll (all-of match), companyId, searchQuery (substring over name/email/title), and contactedSince (ISO-8601 — narrows to contacts NOT contacted since that timestamp). Combine fields and they AND together.

crm:write
Input schema
namestringrequired
descriptionstringoptional
filterobjectrequired
crm_update_segmentCRM: Update segment
admindestructive

Patch a segment: rename, edit description, or replace the filter.

crm:write
Input schema
idstringrequired
patchobjectrequired
crm_delete_segmentCRM: Delete segment
admindestructive

Delete a segment. Outreach campaigns referencing it will fail until reassigned.

crm:write
Input schema
idstringrequired
crm_list_contacts_in_segmentCRM: List contacts in segment
adminread-only

Resolve a segment to its current contacts. ALWAYS excludes suppressed contacts (do_not_contact OR unsubscribed) AND contacts without a recorded lawful basis (consent_lawful_basis IS NULL). Use this — not crm_list_contacts — to materialize an outreach audience: the suppression and consent floors are non-overridable here.

crm:read
Input schema
idstringrequired
limitintegeroptional
crm_exportCRM: Export data
adminread-only

Export this org's CRM (pipelines and stages, segments, companies, contacts, deals, activities, and relationships) as a portable JSON payload. Pair with `crm_import` on another Munin server to move a CRM between self-hosted and cloud. Feed the returned `records` straight into `crm_import`.

crm:read
Input schema
(no fields documented)
crm_importCRM: Import data
admindestructive

Import CRM `records` produced by `crm_export` (typically from another Munin server). Records are imported in dependency order (pipelines → segments → companies → contacts → deals → activities → relationships). Pipelines are upserted by slug, segments by name, companies by domain (else name), and contacts by email — so re-running is idempotent. Deals, activities, and relationships get fresh ids each run with their parents resolved through the `idMap`. Returns counts and an `idMap` (source id → id on this server); pass that `idMap` back into later imports so dependent records resolve their parents.

crm:write
Input schema
recordsobjectrequired
idMapobjectoptional
kb_list_spacesKB: List spaces
adminread-only

List knowledge-base spaces in your org.

kb:read
Input schema
(no fields documented)
kb_create_spaceKB: Create space
admindestructive

Create a new knowledge-base space. Slug must be unique within your org and only contain lowercase letters, digits and hyphens.

kb:write
Input schema
namestringrequired
slugstringrequired
descriptionstringoptional
kb_list_documentsKB: List documents
adminread-only

List knowledge-base documents in your org, newest-updated first. Optionally filter by space or tag.

kb:read
Input schema
spaceIdstringoptional
tagstringoptional
limitintegeroptional
kb_get_documentKB: Read document
adminself-serviceread-only

Read one knowledge-base document, including its full body, tags, and current version. End-user agents see only documents whose `audiences` includes `'self_service'`.

kb:read
Input schema
idstringrequired
kb_get_document_by_slugKB: Read document by slug
adminread-only

Read a knowledge-base document by its space slug and document slug — used when a stable identifier (e.g. 'agent-runtime/system-prompt') is needed instead of the document UUID. Returns null when the document does not exist.

kb:read
Input schema
spaceSlugstringrequired
slugstringrequired
kb_create_documentKB: Create document
admindestructive

Create a knowledge-base document inside a space. Body should be markdown. Set `audiences: ['admin', 'self_service']` to expose it to end-user agents; defaults to `['admin']` (admin-only).

kb:write
Input schema
spaceIdstringrequired
titlestringrequired
bodystringrequired
audiencesenum[]optional
tagsstring[]optional
slugstringoptional
kb_exportKB: Export data
adminread-only

Export this org's knowledge base (spaces and non-system documents) as a portable JSON payload. Pair with `kb_import` on another Munin server to move a knowledge base between self-hosted and cloud. Embeddings are not included — they are regenerated on import. Feed the returned `records` straight into `kb_import`.

kb:read
Input schema
(no fields documented)
kb_importKB: Import data
admindestructive

Import knowledge-base `records` produced by `kb_export` (typically from another Munin server). Spaces are upserted by slug and documents by (space, slug) — or (space, title) when a document has no slug — so re-running is idempotent. Embeddings are regenerated here. Returns counts and an `idMap` (source id → id on this server); pass that `idMap` back into later imports so dependent records resolve their parents.

kb:write
Input schema
recordsobjectrequired
idMapobjectoptional
kb_import_websiteKB: Import website
admindestructive

Crawl a public website and populate the knowledge base from it: one KB document per page. Runs asynchronously on the curator queue — returns a job id you can track with the curator jobs control plane. Pass a homepage URL (a bare domain like `example.com` is accepted). The URL must be publicly reachable; localhost and private/internal addresses are rejected. Re-importing the same URL while a scrape is still pending returns the in-flight job instead of starting a second one. By default the import also synthesizes a `company-profile` KB document (slug `company-profile`) that seeds the chat widget — appropriate when importing your own company's site. Set `synthesizeCompanyProfile: false` when importing third-party or topic pages that are NOT your company's website, so the import doesn't overwrite your company profile with unrelated content. Reconciliation is on by default: after a healthy crawl, previously imported pages that are no longer on the site (re-checked individually and confirmed to return 404/410) are deleted from the knowledge base, so a refresh prunes removed pages. Set `reconcile: false` to import additively without pruning.

kb:write
Input schema
urlstringrequired
synthesizeCompanyProfilebooleanoptional
reconcilebooleanoptional
kb_import_website_statusKB: Website import status
adminread-only

Check the progress of a website import started with `kb_import_website`, by the job id it returned. Status is one of `pending` (queued or running), `done` (finished — `summary` reports how many documents were imported), `failed_retryable`/`dead` (will retry / gave up), or `failed`. While `pending`, poll again after a short delay.

kb:read
Input schema
jobIdstringrequired
kb_update_documentKB: Update document
admindestructive

Update a knowledge-base document. Pass `ifVersion` (the current version you read) for optimistic concurrency; the call fails if it has changed.

kb:write
Input schema
idstringrequired
ifVersionintegerrequired
titlestringoptional
bodystringoptional
audiencesenum[]optional
tagsstring[]optional
kb_delete_documentKB: Delete document
admindestructive

Delete a knowledge-base document. Pass `ifVersion` for optimistic concurrency. Cascades to chunks and versions. System-managed docs (e.g. the seeded `agent-runtime` prompts) cannot be deleted — edit their content with `kb_update_document` instead.

kb:write
Input schema
idstringrequired
ifVersionintegerrequired
kb_list_versionsKB: List document versions
adminread-only

List all prior versions of a knowledge-base document, newest first.

kb:read
Input schema
documentIdstringrequired
kb_propose_curation_candidateKB: Propose curation candidate
admindestructive

File a draft FAQ-style document into the `kb-curation-inbox` KB space (admin audience only). Used after a curation pass over resolved-handover conversations. The space is created on first use. See `skill://kb/review-content` for the procedure. The candidate is NOT visible to end-user agents until it's promoted with `kb_publish_curation_candidate`.

kb:write
Input schema
subjectstringrequired
draftBodystringrequired
sourceConversationIdstringoptional
sourceMessageIdsstring[]optional
proposedTargetSpaceSlugstringoptional
kb_publish_curation_candidateKB: Publish curation candidate
admindestructive

Promote a reviewed curation candidate into a target KB space. Copies the doc to the target space (default audiences `['admin', 'self_service']` so the self-service agent can find it), drops the `curation`/`candidate` tags, and removes the candidate from the inbox. The target space must already exist.

kb:write
Input schema
candidateDocumentIdstringrequired
targetSpaceSlugstringrequired
audiencesenum[]optional
kb_restore_versionKB: Restore document version
admindestructive

Roll a document back to an earlier version. Creates a new current version with that historical content.

kb:write
Input schema
documentIdstringrequired
versionintegerrequired
ifVersionintegerrequired
outreach_list_campaignsOutreach: List campaigns
adminread-only

List outbound-campaign definitions for this org. Each row carries the brief, the targeted CRM segment, the email channel used to send, cadence rules, CTA URL, the enabled flag, and the two automation flags: `autoDraftInitial` (the weekly curator drafts first-touch emails only when true) and `autoDraftReplies` (replies to inbound prospect messages are auto-drafted only when true). The draft-initial curator only drafts proposals for `enabled = true` campaigns with `autoDraftInitial = true`.

outreach:read
Input schema
(no fields documented)
outreach_get_campaignOutreach: Read one campaign
adminread-only

Read a single campaign by id, including brief and cadence rules.

outreach:read
Input schema
idstringrequired
outreach_create_campaignOutreach: Create campaign
admindestructive

Create an outbound-campaign definition. Operators write `brief` as a one-paragraph human description of intent (the curator personalises per contact from this). `segmentId` chooses the audience; the curator calls `crm_list_contacts_in_segment` (which always enforces suppression+consent floor) to materialize it. `channelId` must reference an email channel. New campaigns default `enabled: false` so nothing sends until you flip it on. Automation is opt-in per behavior: `autoDraftInitial` defaults false (the weekly curator does not draft first-touch emails until you set it true — draft manually otherwise), while `autoDraftReplies` defaults true (replies to inbound prospect messages are auto-drafted for review).

outreach:write
Input schema
namestringrequired
briefstringrequired
segmentIdstringrequired
channelIdstringrequired
cadenceRulesobjectoptional
ctaUrlunknownoptional
enabledbooleanoptional
autoDraftInitialbooleanoptional
autoDraftRepliesbooleanoptional
unsubscribeRequiredbooleanoptional
outreach_exportOutreach: Export data
adminread-only

Export this org's outbound campaigns and their queued proposals as a portable JSON payload. Pair with `outreach_import` on another Munin server. Campaigns reference a CRM segment and a conversation channel, and proposals reference CRM contacts/conversations — so export and import CRM and Conversations first, and thread their `idMap` into `outreach_import`.

outreach:read
Input schema
(no fields documented)
outreach_importOutreach: Import data
admindestructive

Import outreach `records` produced by `outreach_export`. Campaigns are upserted by name and proposals by (campaign, contact, kind), so re-running is idempotent. Segment, channel, contact and conversation foreign keys are resolved through the supplied `idMap` (pass the idMap returned by the CRM and Conversations imports). Campaigns are imported **disabled** — re-enable them after re-entering the channel credentials. Returns counts plus the merged `idMap`.

outreach:write
Input schema
recordsobjectrequired
idMapobjectoptional
outreach_update_campaignOutreach: Update campaign
admindestructive

Patch fields on a campaign — rename, swap segment, adjust cadence, toggle enabled, or toggle the automation flags `autoDraftInitial` (weekly first-touch drafting) and `autoDraftReplies` (auto-drafting replies to inbound prospect messages).

outreach:write
Input schema
idstringrequired
patchobjectrequired
outreach_list_proposalsOutreach: List proposals
adminread-only

List drafted outreach proposals. Defaults to all statuses. The draft-initial curator queries `status: "pending", kind: "initial"` filtered by `(campaignId, contactId)` to dedupe before drafting a new candidate. The operator review surface queries `status: "pending"`. In hosts that support MCP Apps this renders an interactive review panel with per-proposal approve/dismiss actions.

outreach:read
Input schema
statusenumoptional
campaignIdstringoptional
kindenumoptional
contactIdstringoptional
limitintegeroptional
outreach_approve_proposalOutreach: Approve proposal
admindestructive

Approve one pending outreach proposal, which sends it: an initial proposal creates the outbound conversation and sends the first email (with CTA and unsubscribe footer per campaign settings) via the campaign's channel; a reply proposal sends the draft verbatim on its existing conversation. Fails if the proposal is not pending, or if the campaign is disabled or the contact became suppressed since drafting. Returns the proposal with `status: "sent"`, `conversationId`, and `sentMessageId`.

outreach:write
Input schema
idstringrequired
outreach_dismiss_proposalOutreach: Dismiss proposal
admindestructive

Dismiss one pending outreach proposal without sending, optionally recording a reason. The decision (actor and timestamp) is kept on the proposal for audit. Fails if the proposal is not pending. Returns the proposal with `status: "dismissed"`.

outreach:write
Input schema
idstringrequired
reasonstringoptional
outreach_propose_initialOutreach: Propose initial
admindestructive

File one drafted initial outreach email per (campaign, contact) for human approval. Idempotent: re-proposing the same (campaign, contact, kind=initial) throws when a pending draft already exists, or when the contact already has a sent or approved first-touch in this campaign (they were already reached) — call `outreach_list_proposals` first to dedupe. Suppression and consent are re-checked at approve-time too; this tool refuses up-front if the contact is already suppressed.

outreach:write
Input schema
campaignIdstringrequired
contactIdstringrequired
draftSubjectstringoptionalRequired for email campaigns; omit for voice campaigns where the call has no subject.
draftBodystringrequiredFor email campaigns: the email body. For voice campaigns: the opening line / talking-points the AI agent should use when the call connects.
evidenceobjectoptional
proposedSendAtstringoptional
outreach_propose_replyOutreach: Propose reply
admindestructive

File a drafted reply to an inbound message on an outreach-originated conversation, for human approval. The conversation must have an `outreachCampaignId` set (it's an outreach conversation) and a CRM contact resolvable by email. Idempotent: re-proposing while a pending reply exists for the same conversation throws — the operator should approve or dismiss the existing one first. Reply approvals send via `conv_send_message` on the existing conversation; no unsubscribe footer is appended (replies thread inside the existing email chain that already carries the unsubscribe link).

outreach:write
Input schema
conversationIdstringrequired
draftBodystringrequired
evidenceobjectoptional
webhooks_listWebhooks: List
adminread-only

List all webhook subscriptions for your org. Secrets are not returned — they are only shown once at creation. Use webhooks_rotate_secret if you lost it.

webhooks:read
Input schema
(no fields documented)
webhooks_createWebhooks: Create
admindestructive

Create a webhook subscription. `url` must be https://. `events` is an array of event type strings (e.g. ["cms.entry.published"]); omit or pass an empty array to subscribe to all events. The response includes a one-time `secret` of the form `whsec_…` — store it now; it cannot be retrieved later. Deliveries are signed with `x-munin-signature: sha256=<HMAC-SHA256(body, secret)>` and include `x-munin-event`, `x-munin-delivery-id` headers. The signed body carries `createdAt` — reject deliveries whose `createdAt` is outside your freshness window, and dedupe on `x-munin-delivery-id`, to defend against replay. Retries: up to 5 attempts with exponential backoff (30s → 8m). Use webhooks_list_event_types to discover known event names.

webhooks:write
Input schema
urlstringrequired
eventsstring[]optional
activebooleanoptional
webhooks_updateWebhooks: Update
admindestructive

Patch a webhook subscription. Pass only the fields you want to change. Replacing `events` overwrites the full array — read first if you mean to append.

webhooks:write
Input schema
idstringrequired
patchobjectrequired
webhooks_deleteWebhooks: Delete
admindestructive

Delete a webhook. Pending deliveries are cascade-deleted; in-flight HTTP attempts finish their current run.

webhooks:write
Input schema
idstringrequired
webhooks_rotate_secretWebhooks: Rotate signing secret
admindestructive

Generate a new `whsec_…` secret for a webhook. The previous secret stops signing deliveries immediately — update your receiver before rotating. The new secret is returned once in the response.

webhooks:write
Input schema
idstringrequired
webhooks_list_deliveriesWebhooks: List deliveries
adminread-only

List delivery attempts for one webhook, newest first. Each row has attempt count, statusCode, durationMs, error, deliveredAt, nextAttemptAt, and a rolled-up `status` (pending / delivered / failed). Filter with `status` and cap with `limit` (default 50, max 200).

webhooks:read
Input schema
webhookIdstringrequired
limitintegeroptional
statusenumoptional
webhooks_list_event_typesWebhooks: List event types
adminread-only

List the known event type strings emitted across modules (cms, crm, kb, conv, outreach, system). Use the returned strings as input to webhooks_create. The subscription accepts arbitrary strings, so future event types work without a tool update — but this is the canonical catalog today.

webhooks:read
Input schema
(no fields documented)
system_alerts_listSystem alerts: List
adminread-only

List operational alerts for the org. Defaults to open alerts only; pass includeResolved to see history.

system_alerts:read
Input schema
includeResolvedbooleanoptional
limitintegeroptional
system_alerts_getSystem alerts: Get
adminread-only

Read a single alert by id.

system_alerts:read
Input schema
idstringrequired
system_alerts_acknowledgeSystem alerts: Acknowledge
admindestructive

Mark an alert as acknowledged. Does not resolve it; only signals that someone is on it.

system_alerts:write
Input schema
idstringrequired
system_alerts_resolveSystem alerts: Resolve
admindestructive

Manually resolve the open alert for the given source+subject. Writers normally self-resolve; use this when the underlying state can no longer be observed.

system_alerts:write
Input schema
sourceenumrequired
subjectIdunknownoptional
feedback_createFeedback: Create
admindestructive

Submit feedback about Munin. Stays local until an org admin approves it; dismissal deletes the item. Set includeOrgName / includeUserName to attach attribution; both default false.

feedback:write
Input schema
titlestringrequired
bodystringrequired
appScopeenumoptional
includeOrgNamebooleanoptional
includeUserNamebooleanoptional
feedback_listFeedback: List pending
adminread-only

List feedback items in the local outbox awaiting admin action.

feedback:read
Input schema
(no fields documented)
feedback_getFeedback: Get one
adminread-only

Read a single feedback item by id.

feedback:read
Input schema
idstringrequired
feedback_approveFeedback: Approve and forward
admindestructive

Approve a feedback item: transmits the contents to Munin developers and deletes the local row on success. Attribution is included only when the submitter opted in.

feedback:write
Input schema
idstringrequired
feedback_dismissFeedback: Dismiss
admindestructive

Dismiss (delete) a pending feedback item. Nothing is sent to Munin.

feedback:write
Input schema
idstringrequired
feedback_voteFeedback: Vote on roadmap item
admindestructive

Cast this instance's vote on a published roadmap item, optionally attaching a short comment. Idempotent: a second call from the same instance returns { alreadyVoted: true } without inflating the count. Throws feedback_item_not_found if the id is unknown or the item is not public, and feedback_vote_quota_exceeded if the per-instance quota has been hit.

feedback:write
Input schema
idstringrequired
commentstringoptional

Self-service tools 8

Visible to delegated end-user tokens. Scoped to one principal and read-only or contributor at most.

pingPing the MCP server
adminself-serviceread-only

Verify the MCP pipe; echoes a message and returns the resolved org and actor type.

Input schema
messagestringoptional
conv_request_humanConv: Request a human teammate to take over
self-servicedestructive

Flag the current conversation as needing human attention. Use this when you can't answer the end-user's question on your own — pricing exceptions, account-specific issues you can't verify, anything sensitive. Pass the exact `conversationId` you were given in the system context. Sets a "needs human attention" flag on the conversation (pinning it to the top of the team's dashboard) and posts an internal note recording your `reason`. Pass `suggestedReply` ONLY when you actually have a substantive answer to propose — write it as the reply you would send the end-user to resolve their question, so a teammate can review, edit, and send it. It must NOT be the "a teammate will follow up" acknowledgement, and must NOT repeat the message you are sending the end-user this turn. If you are escalating precisely because you don't have an answer, OMIT `suggestedReply` entirely — an empty draft is far better than one that just parrots your deferral. After calling this, do not keep generating replies on your own — let the user know a teammate will follow up, then stop. The flag clears once a teammate replies. The end-user does not see the system note or the suggested reply — only the team does.

conv:write
Input schema
conversationIdstringrequired
reasonstringoptional
suggestedReplystringoptional
conv_request_callbackConv: Place a phone call to the user in this conversation
self-servicedestructive

Place an outbound phone call to the contact in this conversation. Use this only when the user has asked to be called — e.g. "can you call me?". The call goes to the phone number already on file for this conversation's contact; you cannot specify an arbitrary number. The org must have an active Vapi voice channel configured. After requesting the call, tell the user briefly that a call is on the way and stop replying — the rest of the conversation happens on the phone.

conv:write
Input schema
conversationIdstringrequired
channelIdstringoptional
crm_get_my_contactCRM: Read my contact
self-serviceread-only

Read the CRM contact linked to the calling end-user. RLS restricts visibility to that single row.

crm:read
Input schema
(no fields documented)
crm_update_my_contactCRM: Update my contact
self-servicedestructive

Update the calling end-user's own contact record. Only basic personal fields (name, phone, address) are editable from this surface — tags, ownership, custom fields, and AI fields are admin-only.

crm:write
Input schema
namestringoptional
phonestringoptional
addressstringoptional
crm_log_my_activityCRM: Log activity as end-user
self-servicedestructive

Record an activity attributed to the calling end-user agent (e.g. a voice agent logging "spoke with customer for 4m, follow-up needed"). Auto-scoped to the end-user's own CRM contact when one exists.

crm:write
Input schema
typeenumrequired
subjectstringoptional
bodystringoptional
metadataobjectoptional
kb_get_documentKB: Read document
adminself-serviceread-only

Read one knowledge-base document, including its full body, tags, and current version. End-user agents see only documents whose `audiences` includes `'self_service'`.

kb:read
Input schema
idstringrequired