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

Tools your agent can call.

Munin exposes 216 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 204

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 id, org name and actor type.

Input schema
messagestringoptional
identity_resolveIdentity: Resolve
adminread-only

Find the end-user identity that an email address, phone number, external id, or analytics visitor id belongs to. An end user is the durable per-person record created the first time someone reaches the org on any channel — inbound email, chat widget, or an analytics identify call — so it exists for people who have no CRM contact yet. Returns `endUserId` plus `matchedOn` naming which identifier matched, and `crmContactId` (null when no CRM contact has been created for this person). Returns `endUserId: null` when nothing matches; it never creates a record. Match order is external id, then email, then phone, then visitor id.

identity:read
Input schema
emailstringoptional
phonestringoptional
externalIdstringoptional
visitorIdstringoptional
identity_getIdentity: Get
adminread-only

Read one end user with a cross-channel summary: which channel types they have written on, how many conversations they have and when the last one was, their linked analytics visitor ids, their page-view and search event counts, and the ids of their CRM contact and conversation contact if those exist. Use it to see everything the org already knows about a person before answering them. Covers only data stored in Munin — orders and bookings live in the customer's own systems and are read separately by email.

identity:read
Input schema
endUserIdstringrequired
analytics_export_configAnalytics: Export tracker configuration
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
canonicalLocalesstring[]optional
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, `allowedOrigins`, or `canonicalLocales`. Ingest already folds trailing slashes and a leading path segment that matches the page's own `lang` attribute, so `canonicalLocales` is only needed when a URL prefix disagrees with that tag (a `/no/…` path on pages declaring `lang="nb-NO"`) or when pages set no `lang` at all; entries are matched against the first path segment, and subject ids that aren't path-shaped are never rewritten. Takes effect on the next event — no site redeploy — and past rows keep the ids they were written with. The bound API key is unchanged — rotate via `analytics_revoke_tracker` + `analytics_create_tracker`.

analytics:write
Input schema
trackerIdstringrequired
namestringoptional
allowedOriginsstring[]optional
requireVerifiedIdentitybooleanoptional
canonicalLocalesstring[]optional
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 sign the identity payload — the fields `['mn.identity.v1', externalId, visitorId, email]`, each prefixed with its UTF-8 byte length, concatenated. The hash binds the visitor, so read `visitorId` from `window.mn.analytics.getVisitorId()` first, then call `window.mn.analytics.identify(externalId, userHash, { email })` from the browser. The email is optional and may be omitted from both the payload and the call, but including it is what links a visitor to the identity the email channel created for the same address. See `skill://analytics/identify-visitors`. 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: List 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 `trackerId` (from `analytics_list_trackers`) to report on a single site or app instead of every tracker in the org summed together. 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
trackerIdstringoptional
endUserIdstringoptional
contactIdstringoptional
analytics_list_top_countriesAnalytics: List top countries by visitors
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`), `source`, or `trackerId` (from `analytics_list_trackers`, to report on a single site or app instead of every tracker summed together) to scope.

analytics:read
Input schema
subjectTypestringoptional
sinceDaysintegerrequired
limitintegerrequired
sourceenumoptional
trackerIdstringoptional
analytics_list_traffic_sourcesAnalytics: List 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. Pass `trackerId` (from `analytics_list_trackers`) to attribute one site or app on its own instead of every tracker in the org summed together.

analytics:read
Input schema
subjectTypestringoptional
sinceDaysintegerrequired
limitintegerrequired
sourceenumoptional
trackerIdstringoptional
analytics_list_referrer_hostsAnalytics: List 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. Pass `trackerId` (from `analytics_list_trackers`) to scope to one site or app instead of every tracker in the org summed together — which also makes `excludeHost` meaningful when the org runs several domains.

analytics:read
Input schema
subjectTypestringoptional
excludeHoststringoptional
sinceDaysintegerrequired
limitintegerrequired
sourceenumoptional
trackerIdstringoptional
analytics_get_views_over_timeAnalytics: Get 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. Pass `trackerId` (from `analytics_list_trackers`) for one site or app on its own; without it every tracker in the org is summed into one line. In hosts that support MCP Apps this renders an inline time-series chart.

analytics:read
Input schema
subjectTypestringoptional
subjectIdstringoptional
sinceDaysintegerrequired
sourceenumoptional
trackerIdstringoptional
endUserIdstringoptional
contactIdstringoptional
analytics_get_subject_engagementAnalytics: Get 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. Pass `trackerId` (from `analytics_list_trackers`) when the same subject id is served by more than one tracked site and you want them apart.

analytics:read
Input schema
subjectTypestringrequired
subjectIdstringrequired
sinceDaysintegerrequired
trackerIdstringoptional
endUserIdstringoptional
contactIdstringoptional
analytics_get_funnelAnalytics: Get 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). Pass `trackerId` (from `analytics_list_trackers`) to build the funnel from one site or app only; without it, steps match events from every tracker in the org. 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
trackerIdstringoptional
analytics_get_contact_journeyAnalytics: Get a contact’s view journey
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.analytics.identify(externalId, userHash, { email })`. 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. Pass `trackerId` (from `analytics_list_trackers`) to narrow the timeline to one site or app.

analytics:read
Input schema
contactIdstringoptional
endUserIdstringoptional
sinceDaysintegerrequired
limitintegerrequired
trackerIdstringoptional
analytics_list_zero_result_searchesAnalytics: List zero-result searches
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. Pass `trackerId` (from `analytics_list_trackers`) to see one site's or app's searches on their own; searches Munin ran itself through the CMS delivery API carry no tracker and are excluded by that filter.

analytics:read
Input schema
subjectTypestringoptional
sinceDaysintegerrequired
limitintegerrequired
trackerIdstringoptional
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. `localized` can be turned on at any time and existing entries keep the locale they already have; turning it off is refused while the collection holds entries in more than one locale.

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 as summaries: identifiers, status, derived title, short fields verbatim, and long text shortened to a lead with a word count in `fieldSummary`. Pass `ids` to read up to 50 specific entries in one call, or `fields` to return named fields verbatim. `truncated: true` marks an entry whose full field values were withheld from the summary. 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
idsstring[]optional
statusenumoptional
localestringoptional
limitintegeroptional
fieldsstring[]optional
cms_get_entryCMS: Read entry
adminread-only

Read one entry in full, including complete long-text fields. 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. Published entries are stamped with `publishedAt` — pass one to preserve the original date when migrating existing content. Slugs are unique per (collection, slug, locale), so each locale can have its own slug — when the entry is a translation of an existing one, pass `translationOf` with that entry's id to link them.

cms:write
Input schema
collectionstringrequired
slugstringrequired
localestringoptional
translationOfstringoptionalId of an existing entry in the same collection that this entry is the translation of. Joins that entry's translation group, so both locales are treated as the same content even when their slugs differ.
dataobjectrequired
statusenumoptional
publishedAtstringoptionalOriginal publication timestamp (ISO 8601). Set this when migrating existing content so the entry keeps its real publication date; defaults to now.
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 (now, or the `publishedAt` you pass for migrated content) and fires cms.entry.published.

cms:write
Input schema
idstringrequired
ifVersionintegerrequired
publishedAtstringoptionalOriginal publication timestamp (ISO 8601). Set this when migrating existing content so the entry keeps its real publication date; defaults to now.
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_entry_translationsCMS: List entry translations
adminread-only

List every locale variant of one entry — the entries sharing its translation group — with each variant's own slug, status and id. Each locale carries its own slug, so this is how you find the Norwegian URL of an English entry, build a language switcher, or see which locales are still missing.

cms:read
Input schema
entryIdstringrequired
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. In hosts that support MCP Apps this renders a thumbnail gallery with per-asset usage lookup.

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_search_entriesCMS: Search entries
adminread-only

Hybrid full-text + semantic search across CMS entries. Each hit carries a match excerpt, the derived title, and summarized field data — long text is shortened to a lead with a word count in `fieldSummary`. Returns drafts and published; the public delivery API runs the same engine but hard-filters to published-only.

cms:read
Input schema
querystringrequired
collectionstringoptional
statusenumoptional
localestringoptional
limitintegeroptional
includeenum[]optionalSet to ["references"] to expand reference fields into the referenced entries.
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. Entries that shared a `translationGroupId` on the source stay linked as locale variants here; payloads exported before translation groups existed fall back to grouping by (collection, slug). 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, topic, handover state (`active` / `resolved` / `never`), or `since` an ISO timestamp. `handover: "resolved"` plus `since` is the set a knowledge-curation pass works from: questions a human answered inside the window.

conv:read
Input schema
statusenumoptional
assigneeUserIdstringoptional
topicIdstringoptional
endUserIdstringoptionalEnd-user identity id (from `identity_resolve`); keeps only conversations belonging to that person, across every channel they've used.
handoverenumoptional`active` = waiting on a human right now, `resolved` = a handover was answered and cleared, `never` = no handover on record.
sincestringoptionalISO 8601 timestamp; keeps only conversations whose last message is at or after it.
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. A suggested reply is a proposal for the next outbound message: once any public message goes out on the conversation, it is retired and the team is no longer offered 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_get_email_open_statsConv: Read email open stats
adminread-only

Aggregate email open tracking per email channel over a recent window (default 30 days, max 365). Returns for each channel the number of messages delivered in the window, how many of those were opened at least once, the total open count, and the resulting open rate, plus org-wide totals. `trackOpens` reports whether the channel currently embeds the tracking pixel — a channel with tracking off records no opens, so its rate reads 0 rather than "nobody opened these". Open tracking is best-effort in general: only messages with an HTML part carry a pixel, clients that block remote images never report an open, and privacy proxies such as Apple Mail Privacy Protection pre-fetch images and inflate the count.

conv:read
Input schema
channelIdstringoptionalRestrict to one email channel. Omit to cover every email channel in the org.
sinceDaysintegeroptionalWindow size in days, counted back from now. Defaults to 30.
conv_list_channelsConv: List conversation channels
adminread-only

List conversation channels of every kind configured for your org — email, chat (widget), SMS and voice.

conv:read
Input schema
(no fields documented)
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. An applied cut emits `conversation.message.body_revised`, which re-syncs the message's mirrored copy in connected operator bridges (Slack) so the signature disappears there too.

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`. Messages with `authorType: "system"` are always stored as internal staff-only notes regardless of the `internal` flag in the payload, and each coercion is reported in `warnings`. 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_configure_email_channelConv: Configure an email channel
admindestructive

Create or update an email channel's transport configuration with the non-secret fields only. SMTP / IMAP passwords are rejected here: the channel is created inactive and the response includes a one-time link for a human to enter the passwords in the dashboard — the channel activates once they are saved. Updating a channel that is currently deactivated (for example after repeated inbound polling failures) re-tests the stored credentials: the channel is reactivated when SMTP and IMAP both connect, and otherwise stays deactivated with the connection errors in the `probe` field of the response. Set `outbound.provider: 'mailer'` to send via Munin's configured Resend mailer instead of a custom SMTP host (no password needed, channel is active immediately). Set `defaultAgentMode: 'draft_only'` so the agent answers into an internal draft that a human reviews and sends, instead of replying to the sender itself — use it to run an inbox with a human in the loop, or on an outreach-only inbox where a reply must never be 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_channel_testConv: Send a 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_request_channel_credentialsConv: Request a channel credential link
admindestructive

Return a one-time link a human opens to enter a channel’s secret credentials in the dashboard — secrets are never accepted in a conversation. Works for any channel kind: email (SMTP/IMAP passwords) as well as voice and SMS vendor keys. conv_configure_email_channel and conv_configure_voice_sms_channel already return this link on create; use this tool to mint a fresh link when one expired or to rotate the stored secrets. The link expires after 24 hours.

conv:write
Input schema
channelIdstringrequired
conv_create_widget_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_update_widget_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_rotate_widget_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_rotate_widget_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_voice_sms_vendorsConv: List voice/SMS 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_voice_sms_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 channel’s vendor offers using the channel’s stored credentials — e.g. Threll workers, Vapi assistants — so you can pass a valid id to conv_configure_voice_sms_channel instead of guessing. The channel must have completed its credential link first. Returns option `groups` (e.g. `workers`, `assistants`), each with `{ value, label, hint }`.

conv:read
Input schema
channelIdstringrequiredDiscover options for an existing channel using its stored credentials.
conv_configure_voice_sms_channelConv: Configure a voice/SMS channel
admindestructive

Create or update a voice or SMS channel for any supported vendor. Pass `vendor` + the vendor’s non-secret `config` fields (see conv_list_voice_sms_vendors). Secret fields are rejected here: creating returns a pending channel plus a one-time link for a human to enter the secrets in the dashboard — the channel activates once they are saved and verified. Pass `channelId` to update; omit to create. `defaultAgentMode` applies to SMS channels only.

conv:write
Input schema
vendorstringrequiredChannel vendor, e.g. "vapi", "threll", "twilio", "messagebird". Call conv_list_voice_sms_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.
defaultAgentModeenumoptionalHow the agent handles inbound messages on this channel: 'auto' replies directly, 'draft_only' files a draft for a human, 'off' does neither. SMS channels only — an inbound call is run by the vendor's assistant. Set 'draft_only' on an outreach-only number so replies are never auto-sent.
configobjectrequiredVendor-specific configuration object with the non-secret fields only — conv_list_voice_sms_vendors marks which fields are secret. Secret fields are rejected here; they are entered by a human through the credential link returned on create.
conv_test_voice_sms_channelConv: Test a voice/SMS channel’s stored credentials
admindestructive

Verify a voice or SMS channel’s stored credentials with its vendor (no message sent). The result shape is vendor-specific. Email channels are tested with conv_test_email_channel instead.

conv:write
Input schema
channelIdstringrequired
conv_send_sms_channel_testConv: Send a real test SMS on a channel
admindestructive

Send a real test SMS through an SMS channel (Twilio, MessageBird), addressed to `to`. Useful for end-to-end deliverability checks. Voice channels have no test send — their credentials are checked with conv_test_voice_sms_channel, and a test call is placed by a human from the dashboard. Email channels use conv_send_email_channel_test.

conv:write
Input schema
channelIdstringrequired
tostringrequired
bodystringoptional
connectors_list_vendorsConnectors: List supported vendors
adminread-only

List the third-party systems Munin can connect to, grouped by domain (commerce, bookings, seo) with the config fields each vendor requires. Use it to see what credentials are needed before creating a connection. Vendors marked `oauth` are authorized by redirect — store their client credentials, then finish with connectors_get_authorize_url.

connectors:read
Input schema
(no fields documented)
connectors_list_connectionsConnectors: List connections
adminread-only

List this org’s connections to third-party systems with domain, non-secret settings, active state, and the result of the last credential test. Secrets are never returned.

connectors:read
Input schema
(no fields documented)
connectors_create_connectionConnectors: Create a connection
admindestructive

Create a connection to a third-party system. `config` takes the vendor’s non-secret fields only — connectors_list_vendors returns the exact fields and marks which are secret. Secret fields are rejected here: the connection is created pending and the response includes a one-time link for a human to enter the secrets in the dashboard. The vendor determines the domain (commerce, bookings, mcp). Connection names must be unique within the org. For the `custom-mcp` vendor the connected server is a customer-facing tool source, not a toolbox for admin agents: it exposes nothing until specific tool names are listed in its `allowedTools` config.

connectors:write
Input schema
vendorstringrequired
namestringrequired
configobjectoptional
connectors_request_credentialsConnectors: Request a credential link
admindestructive

Return a one-time link a human opens to enter a connection’s secret credentials in the dashboard, so the secret is never pasted into a conversation. Use it for a pending connection created without its secret. The link expires after 24 hours.

connectors:write
Input schema
connectionIdstringrequired
connectors_get_authorize_urlConnectors: Get an authorization link
adminread-only

Return the vendor OAuth link that grants Munin access for a connection, for vendors that authorize by redirect instead of a pasted key (connectors_list_vendors marks which). A human must open and approve it in a browser; it expires after 10 minutes, so call again for a fresh one. Use it to finish a pending connection once its client credentials are stored, or to reconnect one whose credentialState is expired. Fails for vendors that use static credentials.

connectors:read
Input schema
connectionIdstringrequired
connectors_update_connectionConnectors: Update a connection
admindestructive

Rename, activate/deactivate, or reconfigure a connection. When passing `config`, supply the full non-secret vendor config; the stored secret values are kept. Secret fields are rejected here — to rotate a secret, delete the connection and create it again, entering the new secret through the credential link.

connectors:write
Input schema
connectionIdstringrequired
namestringoptional
configobjectoptional
activebooleanoptional
connectors_list_server_toolsConnectors: List a custom MCP server’s tools
adminread-only

List the tools a connected custom MCP server offers, each flagged with whether it is currently exposed to customers (`allowed`) and whether the server marks it read-only (`destructive`). Use it to see what a server provides before choosing which tools customers may reach. Only applies to vendors with a selectable tool list, such as custom-mcp.

connectors:read
Input schema
connectionIdstringrequired
connectors_set_allowed_toolsConnectors: Set which tools customers may use
admindestructive

Replace the set of tools a connected custom MCP server exposes to customers. Pass the exact tool names from connectors_list_server_tools; anything omitted stops being offered. An empty list leaves the server connected but silent. Tools reach end-users in chat, email and SMS conversations, so list only what a customer should be able to call about themselves.

connectors:write
Input schema
connectionIdstringrequired
toolNamesstring[]required
connectors_delete_connectionConnectors: Delete a connection
admindestructive

Delete a connection and its stored credentials. Lookups through this connection stop working immediately.

connectors:write
Input schema
connectionIdstringrequired
connectors_test_connectionConnectors: Test a connection’s credentials
admindestructive

Verify a connection’s stored credentials against the vendor with a read-only probe (no external data is changed). Records the result on the connection.

connectors:write
Input schema
connectionIdstringrequired
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_lookup_contactCRM: Look up 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_lookup_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_deal_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. Rejects with a conflict when the pair has already been merged: either side carrying `customFields.mergedInto`, or an `applied` proposal already on record for the pair. 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. In hosts that support MCP Apps this renders an interactive review panel with side-by-side contact comparison and per-proposal apply/dismiss actions.

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, reassigns the duplicate's activities, deals and relationships onto the keeper, archives the duplicate (adds `dedup-archived-YYYY-MM` tag, sets `customFields.mergedInto = <keeperId>`, sets `doNotContact: true`), dismisses any other pending proposals that reference the duplicate, and marks the proposal `applied`. The apply is bound to the proposal it was given: `fingerprint` must match the current `mergeFingerprint`, so a proposal whose keeper, patch or confidence changed since it was read is refused with a conflict and stays pending. Throws if the proposal is not in `pending` status.

crm:write
Input schema
idstringrequired
fingerprintstringrequiredThe `mergeFingerprint` carried by the proposal as it was read, binding this apply to that exact keeper and patch.
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, with each document's `sourceUrl` (the public page it came from, or null). Bodies are not included; read one with `kb_get_document`. 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, `sourceUrl` (the public page it came from, or null), 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). Set `sourceUrl` when the document mirrors a public page — a help-centre article, a product page — and answers drawn from it can link there.

kb:write
Input schema
spaceIdstringrequired
titlestringrequired
bodystringrequired
sourceUrlstringoptionalThe public page this document is the knowledge-base copy of, as an absolute http(s) URL. Returned by kb_search and kb_get_document so an answer can link to it.
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_get_website_import_statusKB: Get 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. Omitted fields keep their current value; `sourceUrl: null` clears the recorded source page.

kb:write
Input schema
idstringrequired
ifVersionintegerrequired
titlestringoptional
bodystringoptional
sourceUrlunknownoptionalThe public page this document is the knowledge-base copy of, as an absolute http(s) URL. Omit to leave it as it is; pass null to clear it.
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), for knowledge the KB does not cover at all. Used after a curation pass over resolved-handover conversations. The space is created on first use. See `skill://kb/review-content` for the procedure. To correct or extend a document that already exists, file `kb_propose_curation_revision` instead — it publishes as a new version of that document rather than a second one beside it. The candidate is NOT visible to end-user agents until it's promoted with `kb_publish_curation_candidate`. Fails with `kb_curation_decided` when a candidate from the same source message was already dismissed or published; a decision recorded before per-message curation shipped closes its whole conversation.

kb:write
Input schema
subjectstringrequired
draftBodystringrequired
sourceConversationIdstringoptional
sourceMessageIdsstring[]optional
proposedTargetSpaceSlugstringoptional
kb_propose_curation_revisionKB: Propose curation revision
admindestructive

File a proposed new body for a document that already exists, into the `kb-curation-inbox` KB space (admin audience only). Use it when what you learned corrects, contradicts or extends a document rather than filling a gap — a changed rate, a superseded policy, a missing exception. Pass the full proposed body, not a patch; the reviewer sees it as a diff against the document's current text. Publishing it with `kb_publish_curation_revision` writes a new version of that same document, so `kb_list_versions` and `kb_restore_version` can roll it back. Fails with `kb_curation_decided` when a candidate from the same source message was already dismissed or published.

kb:write
Input schema
revisesDocumentIdstringrequired
draftBodystringrequiredThe full proposed body of the revised document.
subjectstringoptionalDefaults to the revised document's current title.
sourceConversationIdstringoptional
sourceMessageIdstringoptional
kb_list_curation_candidatesKB: List curation candidates
adminread-only

List pending curation candidates awaiting review — drafts filed into the `kb-curation-inbox` space. Each row carries the source conversation id parsed from its tags, plus either a proposed target space slug (a new document) or `revisesDocumentId` with the revised document's current title and version (a proposed new version of an existing document). Bodies are not included; read one with `kb_get_document`. In hosts that support MCP Apps this renders an interactive review panel with per-candidate publish/dismiss actions.

kb:read
Input schema
limitintegeroptional
kb_dismiss_curation_candidateKB: Dismiss curation candidate
admindestructive

Reject a curation candidate: deletes the draft from the `kb-curation-inbox` space and records the decision, with an optional reason. The decision is permanent and scoped to the source conversation — later curation passes are refused when they try to file another candidate from it, so a rejected draft stays rejected instead of reappearing on the next weekly sweep. Pass `ifVersion` (the candidate `version` that was reviewed). Read past decisions with `kb_list_curation_decisions`.

kb:write
Input schema
candidateDocumentIdstringrequired
ifVersionintegerrequiredThe candidate document `version` that was reviewed, binding this dismissal to that exact text.
reasonstringoptional
kb_list_curation_decisionsKB: List curation decisions
adminread-only

List curation candidates that were already decided — dismissed (with the reason, when one was given) or published — newest first. Filter by `outcome` or `sourceConversationId`. A conversation that appears here is closed for curation: `kb_propose_curation_candidate` refuses further candidates from it.

kb:read
Input schema
outcomeenumoptional
sourceConversationIdstringoptional
limitintegeroptional
kb_publish_curation_candidateKB: Publish curation candidate
admindestructive

Promote a reviewed curation candidate into a target KB space as a new document. Copies the doc to the target space (default audiences `['admin', 'self_service']` so the self-service agent can find it), drops the curation tags, and removes the candidate from the inbox. The target space is created from the slug if it does not exist yet. Pass `ifVersion` (the candidate `version` that was reviewed) for optimistic concurrency; if the draft was edited since, the call fails and nothing is published. Refuses a candidate that proposes a revision to an existing document — use `kb_publish_curation_revision` for those.

kb:write
Input schema
candidateDocumentIdstringrequired
targetSpaceSlugstringrequired
ifVersionintegerrequiredThe candidate document `version` that was reviewed, binding this publish to that exact text.
audiencesenum[]optional
kb_publish_curation_revisionKB: Publish curation revision
admindestructive

Apply a reviewed revision candidate to the document it revises, as a new version of that document, then remove the candidate from the inbox. Takes two versions: `ifCandidateVersion` binds the publish to the proposed text that was reviewed, and `ifDocumentVersion` binds it to the document text that was diffed against — if either moved since, the call fails and nothing is written. Roll back with `kb_restore_version`.

kb:write
Input schema
candidateDocumentIdstringrequired
ifCandidateVersionintegerrequiredThe candidate `version` that was reviewed, binding this publish to that exact proposed text.
ifDocumentVersionintegerrequiredThe revised document `version` the proposal was diffed against, so a document edited elsewhere since is not silently overwritten.
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, `sequenceSteps` (ordered follow-up steps drafted by the daily curator when the campaign is enabled; empty means no sequence), CTA URL, the enabled flag, and the automation flags: `autoDraftFirstTouch` (the weekly curator drafts first-touch emails only when true), `autoDraftReplies` (replies to inbound prospect messages are auto-drafted only when true), and `autoCurateEdits` (a human editing a draft before approving it feeds a KB curation pass only when true). The weekly first-touch curator only drafts proposals for `enabled = true` campaigns with `autoDraftFirstTouch = 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, SMS, or voice channel; approving a proposal on an SMS or voice campaign is restricted to a signed-in person in the Munin dashboard. New campaigns default `enabled: false` so nothing sends until you flip it on. Automation is opt-in per behavior: `autoDraftFirstTouch` defaults false (the weekly curator does not draft first-touch emails until you set it true — draft manually otherwise), `autoDraftReplies` defaults true (replies to inbound prospect messages are auto-drafted for review), and `autoCurateEdits` defaults false (when true, a proposal a human edited before approving queues a KB curation pass over what they changed — leave it off unless edits on this campaign tend to correct facts rather than personalise copy). Auto-sending a reply is not an option on any campaign: conversations created by an approved proposal are always set to `draft_only`, whatever the channel default says, so a prospect never receives an unreviewed reply. Optional `sequenceSteps` (email campaigns only) defines a follow-up sequence — each step is a wait period plus a drafting brief; defining steps on an enabled campaign opts it into daily follow-up drafting for threads with no reply.

outreach:write
Input schema
namestringrequired
briefstringrequired
segmentIdstringrequired
channelIdstringrequired
cadenceRulesobjectoptional
sequenceStepsobject[]optional
ctaUrlunknownoptional
enabledbooleanoptional
autoDraftFirstTouchbooleanoptional
autoDraftRepliesbooleanoptional
autoCurateEditsbooleanoptional
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) — plus `sequenceStep` for follow-ups — 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. Proposals that were approved and awaiting a scheduled send on the source server arrive as `pending` with a warning, so no timer follows the data across servers. 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, toggle the automation flags `autoDraftFirstTouch` (weekly first-touch drafting), `autoDraftReplies` (auto-drafting replies to inbound prospect messages) and `autoCurateEdits` (KB curation over what a human changed before approving a draft), or replace `sequenceSteps` (the follow-up sequence; pass the full array, email campaigns only, empty array removes the sequence).

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

List drafted outreach proposals, newest first. Defaults to all statuses and to 25 rows; pass `status` and `limit` to narrow. Rows carry the full `draftSubject` / `draftBody` plus nested `contact`, `campaign` and `delivery` summaries, but not the curator `evidence` payload, which can run to thousands of characters per row — a boolean `hasEvidence` says whether there is any, and `outreach_get_proposal` returns it for one proposal. The first-touch curator queries `status: "pending", kind: "initial"` filtered by `(campaignId, contactId)` to dedupe before drafting a new candidate. The operator review surface queries `status: "pending"`. `status: "approved"` lists approved sends still waiting for their `scheduledSendAt`, soonest first. 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
limitintegeroptionalDefaults to 25.
outreach_get_proposalOutreach: Get proposal
adminread-only

Read one outreach proposal by id, including the full curator `evidence` payload that `outreach_list_proposals` omits — the sources, compliance notes and reasoning recorded when the draft was filed. Also carries the draft, its status and decision history, and the nested `contact`, `campaign` and `delivery` summaries.

outreach:read
Input schema
idstringrequired
outreach_approve_proposalOutreach: Approve proposal
admindestructive

Approve one pending outreach proposal, authorizing it to go out: an initial proposal creates the outbound conversation and delivers the first touch on the campaign's channel — an email (with CTA and unsubscribe footer per campaign settings), an SMS, or an outbound call placed through the channel's voice vendor; a reply or follow-up proposal sends the draft verbatim on its existing conversation. Sends immediately unless a future send time applies, in which case the proposal returns `status: "approved"` with `scheduledSendAt` and a background worker delivers it then, re-checking campaign state, suppression and quiet hours at that moment. The approval is bound to the draft it was given: `fingerprint` must match the proposal's current `draftFingerprint`, so a draft revised since it was read is refused with a conflict and stays pending. Also fails if the proposal is not pending, if the campaign is disabled, if the contact became suppressed since drafting, or — for follow-ups — if the prospect replied after the draft was filed (dismiss it; the reply flow takes over). Returns the proposal with `status: "sent"`, `conversationId` and `sentMessageId` on an immediate send.

outreach:write
Input schema
idstringrequired
fingerprintstringrequiredThe `draftFingerprint` carried by the proposal as it was read, binding this approval to that exact draft.
sendAtunknownoptionalWhen the approved message should go out, ISO-8601 and in the future. Omit to use the time the draft carries in `proposedSendAt`, or to send now when it carries none. Pass null to send now even though the draft proposes a later time.
outreach_cancel_scheduled_sendOutreach: Cancel scheduled send
admindestructive

Call off a scheduled outreach send before the worker delivers it, returning the proposal to `status: "pending"` so it goes back on the review queue with its draft and revision history intact. The approval is cleared, so sending it later takes a fresh approval. Nothing is sent and the contact is not suppressed. Fails when the proposal is not an approved, still-scheduled send — a proposal already delivered cannot be recalled. Takes a required `reason`.

outreach:write
Input schema
idstringrequired
reasonstringrequiredWhy the scheduled send is being called off. Recorded on the audit trail.
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 — an approved send waiting on its schedule has to be called off with `outreach_cancel_scheduled_send` first, which puts it back in `pending`. Returns the proposal with `status: "dismissed"`.

outreach:write
Input schema
idstringrequired
reasonstringoptional
outreach_revise_proposalOutreach: Revise proposal
admindestructive

Rewrite the draft on one pending outreach proposal in place, keeping the same proposal id, campaign, and contact — those three cannot be changed here; a different recipient or campaign is a different proposal. Pass any of `draftSubject`, `draftBody`, `proposedSendAt` plus a required `reason`. The revision is recorded on the proposal: `revisionCount`, `lastRevisedAt`, `lastRevisionReason`, and the revising actor, plus `revisedAfterReviewAt` when someone else had already opened the draft for review before the change. `proposedSendAt` is the send time an operator inherits when they approve without naming one of their own. Fails if the proposal is not pending — an approved send waiting on its schedule has to be called off with `outreach_cancel_scheduled_send` before its draft can change. Returns the revised proposal.

outreach:write
Input schema
idstringrequired
reasonstringrequiredWhy the draft is being changed. Recorded on the proposal and shown to reviewers.
draftSubjectunknownoptional
draftBodystringoptional
proposedSendAtunknownoptional
outreach_withdraw_proposalOutreach: Withdraw proposal
admindestructive

Retract one pending outreach proposal that should no longer be reviewed — a duplicate draft, a prospect who turned out not to qualify, a bounced address. Nothing is sent. This is a neutral retraction, not a rejection: it does not suppress the contact, does not change their consent, and does not stop a campaign sequence (a withdrawn follow-up leaves later steps eligible, unlike a dismissed one). A required `reason` and the withdrawing actor are recorded. Fails if the proposal is not pending. Returns the proposal with `status: "withdrawn"`.

outreach:write
Input schema
idstringrequired
reasonstringrequiredWhy the draft is being retracted, e.g. "duplicate of oprp_… " or "address bounced".
outreach_propose_first_touchOutreach: Propose first touch
admindestructive

File one first-touch outreach draft per (campaign, contact) for human approval — an email body, an SMS body, or the script for an outbound call, depending on the channel the campaign sends on. 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 SMS and voice campaigns, which have no subject.
draftBodystringrequiredFor email campaigns: the email body. For SMS campaigns: the text message, capped at 480 characters, plain text — no markdown, and no opt-out line, which Munin appends. For voice campaigns: the opening line / talking-points the AI agent should use when the call connects.
evidenceobjectoptional
proposedSendAtstringoptionalWhen this draft should ideally go out, ISO-8601. Advisory: the operator who approves it inherits this time unless they name their own, and a time already in the past is treated as send-now.
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
outreach_list_due_followupsOutreach: List due follow-ups
adminread-only

List outreach conversations whose next sequence step is due now. A row is returned when the campaign is enabled with a `sequenceSteps` entry beyond the last sent outbound, the wait period has elapsed with zero inbound replies, the conversation is open and unassigned, the contact is not suppressed, and no pending follow-up/reply or dismissed step blocks it. Campaign cadence rules are also honored: a contact at their `maxPerWeekPerContact` budget of sent touches in the trailing 7 days is held back, and nothing is due on a `blackoutDates` day (quiet hours are a send-time concern and do not gate drafting). Each row carries `conversationId`, `nextStep`, and the step brief — everything needed to draft and file the follow-up via `outreach_propose_followup`. An empty result means no sequence work is due.

outreach:read
Input schema
campaignIdstringoptional
limitintegeroptional
outreach_propose_followupOutreach: Propose follow-up
admindestructive

File a drafted sequence follow-up (step N of the campaign's `sequenceSteps`) on an outreach conversation, for human approval. Follow-up sequences run on email campaigns only. `step` must be the next step for the conversation, its wait period must have elapsed, and the prospect must not have replied — any inbound reply permanently stops the sequence (the reply flow owns the conversation). One pending follow-up per (campaign, contact); a dismissed follow-up permanently stops the sequence for that contact, so operators who dislike the wording should edit-then-approve instead. Approving sends on the existing conversation with no subject or unsubscribe footer (the thread already carries both).

outreach:write
Input schema
conversationIdstringrequired
stepintegerrequired1-based sequence step to file — must be the next step for this conversation.
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)
slack_get_install_urlSlack: Get install link
adminread-only

Return the Slack OAuth link that connects a Slack workspace to this org. The link must be opened and approved in a browser by a workspace admin; it expires after 10 minutes (call again for a fresh one). After installing, pick a channel with slack_set_routing. Fails when the deployment has no Slack app configured (SLACK_CLIENT_ID / SLACK_CLIENT_SECRET).

slack:read
Input schema
(no fields documented)
slack_get_statusSlack: Get status
adminread-only

Show the org's Slack connection: whether the deployment has a Slack app configured, the connected workspace, channel routing, and mirror-delivery counts (pending + failed in the last 24h). The bot token is never returned.

slack:read
Input schema
(no fields documented)
slack_list_channelsSlack: List channels
adminread-only

List the public channels in the org's connected Slack workspace (id, #name, and whether the bot is already a member). Use to pick a channel ID for slack_set_routing without asking the operator to look it up in Slack. Archived and private channels are not included.

slack:read
Input schema
(no fields documented)
slack_set_routingSlack: Set channel routing
admindestructive

Point conversation mirroring at a Slack channel. purpose 'default' receives every conversation as a thread; purpose 'escalations' receives handover alerts (optionally with a mention); purpose 'approvals' receives approval-queue notifications (CRM merge proposals, outreach drafts, KB curation candidates) with approve/dismiss buttons; purpose 'content' receives an announcement whenever a CMS entry is published, linking the live article when the collection has a liveUrl template; convChannelId scopes a route to one source conversation channel (e.g. widget → #support-chat, email → #support-email). Calling again with the same purpose or convChannelId replaces that route. Every route needs its own Slack channel, and a Slack channel can only serve one Munin org. The response includes botInChannel — when false, invite the bot in Slack (/invite) before messages can post.

slack:write
Input schema
slackChannelIdstringrequiredSlack channel ID (e.g. C0123456789), not the #name
purposeenumoptional'default' (all mirrored conversations; required before mirroring starts), 'escalations' (handover alerts; falls back to the default channel when unset), 'approvals' (approval-queue notifications: CRM merge proposals, outreach drafts, KB curation candidates; falls back to escalations, then default), or 'content' (CMS publish announcements; falls back to default)
mentionstringoptionalOptional Slack mention prepended to escalation alerts, e.g. <!here> or <!subteam^S0123456789>
convChannelIdstringoptionalOptional source-channel override: conversations arriving on this Munin conversation channel (see conv_list_channels) mirror into the given Slack channel instead of the default
slack_send_test_messageSlack: Send test message
admindestructive

Post a test message to the configured default Slack channel to verify the connection end-to-end. Fails with a specific error when the workspace is not connected, no default route is set, or the bot has not been invited to the channel.

slack:write
Input schema
(no fields documented)
slack_disconnectSlack: Disconnect workspace
admindestructive

Disconnect the org's Slack workspace. Deletes the stored bot token, channel routing, and all conversation/message thread links; existing Slack messages remain in Slack. Conversations in Munin are unaffected. Reconnect any time with slack_get_install_url.

slack:write
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
commerce_list_customer_ordersCommerce: List a customer’s orders
adminread-only

List a customer’s recent store orders by email (newest first), e.g. while handling their support conversation. `connectionId` is only needed when multiple commerce connections are active.

commerce:read
Input schema
emailstringrequired
connectionIdstringoptional
limitintegerrequired
commerce_lookup_orderCommerce: Look up one order with tracking
adminread-only

Fetch one order for a customer email, including line items and shipment tracking. Identify the order by `orderRef` (from an order listing) or the human-facing `orderNumber` the customer knows. Returns not-found unless the order belongs to that email.

commerce:read
Input schema
emailstringrequired
connectionIdstringoptional
orderRefstringoptional
orderNumberstringoptional
commerce_search_productsCommerce: Search the product catalog
adminself-serviceread-only

Search the connected store’s live product catalog by name or SKU. Returns published products with price range, currency, image, and storefront link. Only published/enabled products are visible. `connectionId` is only needed when multiple commerce connections are active. In hosts that support MCP Apps this renders an inline product gallery.

commerce:read
Input schema
querystringrequired
connectionIdstringoptional
limitintegerrequired
commerce_get_productCommerce: Get one product
adminself-serviceread-only

Fetch one published product from the connected store, including description and per-variant price and availability (`availableForSale`; null when the store doesn’t expose stock). Identify it by `productRef` (from a catalog search) or `sku`. Returns not-found for unpublished products.

commerce:read
Input schema
connectionIdstringoptional
productRefstringoptional
skustringoptional
bookings_list_guest_bookingsBookings: List a guest’s bookings
adminread-only

List a guest’s bookings by email (most recent first), e.g. while handling their support conversation. `connectionId` is only needed when multiple booking connections are active.

bookings:read
Input schema
emailstringrequired
connectionIdstringoptional
limitintegerrequired
bookings_lookup_bookingBookings: Look up one booking
adminread-only

Fetch one booking for a guest email, including party size, start time, and duration. Identify it by `bookingRef` from a listing, or `confirmationCode` where the venue’s system issues one. Returns not-found unless the booking belongs to that email.

bookings:read
Input schema
emailstringrequired
connectionIdstringoptional
bookingRefstringoptional
confirmationCodestringoptional
bookings_check_availabilityBookings: Check availability
adminself-serviceread-only

List open time slots for a date and party size at the connected booking system, so you can offer real times before creating a booking. `connectionId` is only needed when multiple booking connections are active.

bookings:read
Input schema
datestringrequired
partySizeintegerrequired
seatingTimeintegeroptional
connectionIdstringoptional
bookings_create_bookingBookings: Create a booking
admindestructive

Create a booking in the connected booking system for a guest email at a date and time, for a given party size. Check bookings_check_availability first. Returns the new bookingRef.

bookings:write
Input schema
emailstringrequired
datestringrequired
timestringrequired
partySizeintegerrequired
seatingTimeintegeroptional
areaIdintegeroptional
namestringoptional
phonestringoptional
notestringoptional
connectionIdstringoptional
bookings_update_bookingBookings: Update a booking
admindestructive

Change an existing booking (date, time, party size, or note) by `bookingRef`. Only the fields you pass are changed.

bookings:write
Input schema
bookingRefstringrequired
datestringoptional
timestringoptional
partySizeintegeroptional
notestringoptional
connectionIdstringoptional
bookings_cancel_bookingBookings: Cancel a booking
admindestructive

Cancel an existing booking by `bookingRef` in the connected booking system. This cannot be undone.

bookings:write
Input schema
bookingRefstringrequired
connectionIdstringoptional
seo_list_propertiesSEO: List verified search properties
adminread-only

List the verified sites the connected search-engine account can report on. Use it to discover the `siteUrl` values the other seo_* tools accept. `connectionId` is only needed when multiple seo connections are active.

seo:read
Input schema
connectionIdstringoptional
seo_list_queriesSEO: List search queries for a property
adminread-only

List the search queries a property was impressed and clicked for, aggregated over a date window and sorted by impressions. Each row carries impressions, clicks, ctr and avgPosition. Search-engine reporting lags 2–3 days and Bing reports in whole weeks, so the returned `window` is the range actually covered and can be narrower than the `from`/`to` requested; it is null when no data fell in range. Defaults to the last 90 days. `siteUrl` is only needed when the account has multiple verified properties.

seo:read
Input schema
connectionIdstringoptional
siteUrlstringoptional
fromstringoptional
tostringoptional
limitintegerrequired
seo_list_pagesSEO: List search-traffic pages for a property
adminread-only

List the property’s pages by search impressions over a date window, each with impressions, clicks, ctr and avgPosition. Same reporting caveats as seo_list_queries: results lag 2–3 days, Bing aggregates by week, and the returned `window` is the range actually covered. Defaults to the last 90 days.

seo:read
Input schema
connectionIdstringoptional
siteUrlstringoptional
fromstringoptional
tostringoptional
limitintegerrequired
seo_inspect_urlSEO: Inspect one URL’s index status
adminread-only

Fetch what the search engine knows about one URL on a verified property: whether it is indexed, plus whichever of `detail` (the engine’s own coverage state), `httpStatus`, `lastCrawledAt`, `discoveredAt` and `inboundAnchorCount` that engine exposes — the set differs by engine, and a null field means it is not reported rather than zero. Returns not-found when the engine holds no record for the URL.

seo:read
Input schema
connectionIdstringoptional
siteUrlstringoptional
urlstringrequired
seo_submit_urlsSEO: Submit URLs for indexing
admindestructive

Submit up to 500 URLs on a verified property to the search engine for (re)crawling, e.g. after publishing or updating a page. URLs must be under the property. Returns how many were submitted plus the remaining daily and monthly quota; the call is rejected up front when the batch exceeds the quota left, rather than partially submitting. Submissions count against a per-site daily cap, so submit only URLs whose content actually changed. Not every engine offers URL submission — Bing does, Google Search Console does not, and the call fails with a clear message for engines that don’t.

seo:write
Input schema
connectionIdstringoptional
siteUrlstringoptional
urlsstring[]required
feedback_create_itemFeedback: Create a feedback item
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_list_pending_itemsFeedback: List pending feedback items
adminread-only

List feedback items in the local outbox awaiting admin action.

feedback:read
Input schema
(no fields documented)
feedback_get_itemFeedback: Get one feedback item
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_search_roadmapFeedback: Search the public roadmap
adminread-only

Search the public Munin roadmap for items matching a query. Call this before feedback_create_item to find an existing item to vote on instead of filing a duplicate. Returns only items the Munin team has published; pending and rejected items are hidden. sort defaults to votes (highest first); use "recent" for newest. limit is capped at 100.

feedback:read
Input schema
qstringoptional
appScopeenumoptional
statusenumoptional
sortenumoptional
limitintegeroptional
feedback_vote_on_roadmap_itemFeedback: 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 18

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 id, org name 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. A suggested reply is a proposal for the next outbound message: once any public message goes out on the conversation, it is retired and the team is no longer offered it. 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: Request a callback on 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 my activity
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, `sourceUrl` (the public page it came from, or null), and current version. End-user agents see only documents whose `audiences` includes `'self_service'`.

kb:read
Input schema
idstringrequired
commerce_search_productsCommerce: Search the product catalog
adminself-serviceread-only

Search the connected store’s live product catalog by name or SKU. Returns published products with price range, currency, image, and storefront link. Only published/enabled products are visible. `connectionId` is only needed when multiple commerce connections are active. In hosts that support MCP Apps this renders an inline product gallery.

commerce:read
Input schema
querystringrequired
connectionIdstringoptional
limitintegerrequired
commerce_get_productCommerce: Get one product
adminself-serviceread-only

Fetch one published product from the connected store, including description and per-variant price and availability (`availableForSale`; null when the store doesn’t expose stock). Identify it by `productRef` (from a catalog search) or `sku`. Returns not-found for unpublished products.

commerce:read
Input schema
connectionIdstringoptional
productRefstringoptional
skustringoptional
commerce_list_my_ordersCommerce: List my recent orders
self-serviceread-only

List the calling end-user’s recent store orders (newest first). Scoped server-side to the email on the end-user’s own record — other customers’ orders are never visible. `connectionId` is only needed when the org has multiple active store connections.

commerce:read
Input schema
connectionIdstringoptional
limitintegerrequired
commerce_get_my_orderCommerce: Get one of my orders
self-serviceread-only

Fetch one of the calling end-user’s orders, including line items and shipment tracking. Identify it by `orderRef` (from an order listing) or the human-facing `orderNumber` on the order confirmation. Returns not-found unless the order belongs to the calling end-user.

commerce:read
Input schema
connectionIdstringoptional
orderRefstringoptional
orderNumberstringoptional
bookings_check_availabilityBookings: Check availability
adminself-serviceread-only

List open time slots for a date and party size at the connected booking system, so you can offer real times before creating a booking. `connectionId` is only needed when multiple booking connections are active.

bookings:read
Input schema
datestringrequired
partySizeintegerrequired
seatingTimeintegeroptional
connectionIdstringoptional
bookings_list_my_bookingsBookings: List my bookings
self-serviceread-only

List the calling end-user’s restaurant/venue bookings (most recent first). Scoped server-side to the email on the end-user’s own record — other guests’ bookings are never visible. `connectionId` is only needed when the org has multiple active booking connections.

bookings:read
Input schema
connectionIdstringoptional
limitintegerrequired
bookings_get_my_bookingBookings: Get one of my bookings
self-serviceread-only

Fetch one of the calling end-user’s bookings, including party size, start time, and duration. Identify it by `bookingRef` from a listing, or `confirmationCode` where the venue’s system issues one. Returns not-found unless the booking belongs to the calling end-user.

bookings:read
Input schema
connectionIdstringoptional
bookingRefstringoptional
confirmationCodestringoptional
bookings_create_my_bookingBookings: Create my booking
self-servicedestructive

Create a booking for the calling end-user at a date and time for a party size. The booking is made under the end-user’s own email — you cannot book on behalf of anyone else. Check bookings_check_availability first.

bookings:write
Input schema
datestringrequired
timestringrequired
partySizeintegerrequired
seatingTimeintegeroptional
areaIdintegeroptional
namestringoptional
phonestringoptional
notestringoptional
connectionIdstringoptional
bookings_update_my_bookingBookings: Update my booking
self-servicedestructive

Change one of the calling end-user’s own bookings (date, time, party size, or note) by `bookingRef`. Returns not-found unless the booking belongs to the calling end-user.

bookings:write
Input schema
bookingRefstringrequired
datestringoptional
timestringoptional
partySizeintegeroptional
notestringoptional
connectionIdstringoptional
bookings_cancel_my_bookingBookings: Cancel my booking
self-servicedestructive

Cancel one of the calling end-user’s own bookings by `bookingRef`. Returns not-found unless the booking belongs to the calling end-user. This cannot be undone.

bookings:write
Input schema
bookingRefstringrequired
connectionIdstringoptional