Capabilities
One list of everything Median can be told to do, shared by the REST API, the CLI, the MCP server, the in-app assistant and the /c chat command.
Every action in Median is a capability with one name, namespace.method.
Each surface reaches the same list, by the same names, through the same
backend functions, so none can do something the others cannot, apart from the
few the assistant leaves out on purpose.
| Surface | How conversations.reply is called |
|---|---|
| MCP | median.conversations.reply({ conversationId, body }) inside median_run |
| CLI | median conversations reply <id> --body "...", or median call conversations.reply --data '{...}' |
| REST | POST /v1/conversations/{id}/messages, or POST /v1/call/conversations.reply |
| Assistant | A tool on its list. The model meets it as replyToConversation |
| Slack and Discord | /c calls the inbox capabilities. /note and /t use the same note and tool run code as the inbox |
median_docs on the MCP server returns every method with its arguments and
answer. POST /v1/call/{method} and median call take the same names and
arguments, and return the same values.
Roles
A capability runs with the role of whoever is calling: the person behind an OAuth token, the person talking to the assistant, or an admin for a Median key. A member asking for an admin’s change gets the same refusal the dashboard gives. The matrix is in Roles and permissions.
Asks first
Each capability says whether a model acting for somebody has to ask them first. It is written down for every entry, never defaulted.
| Rule | Covers |
|---|---|
| Runs | Reads, and the team’s own work that can be put back: moving a thread, writing a document or a card, drafting a reply, handing a thread back to the agent |
| Asks | Anything that messages customers in the team’s name, deletes for good, hands out or takes back access (revoking an invitation runs), changes settings or integrations, or opens issues in another tracker |
| Asks when | signals.setStatus, tasks.setStatus and tasks.adopt ask when the status is done, since that tells every reporter. customers.update asks when it sets or clears the email, since that is where their replies go |
What that means on each surface:
| Surface | Effect |
|---|---|
| REST, the CLI | Nothing. A person calling the API is making the call themselves |
| MCP | The median_run description lists every method that asks, by namespace, and tells the client to check with the person before calling one |
| Assistant | The call parks as Needs approval until the person says yes. A call that asks is refused if it ever runs unapproved, so no tool can skip the rule |
The list
Methods that ask first are in the last column.
| Namespace | Methods | Asks first |
|---|---|---|
billing |
overview, usage, invoices, logs, logEntry, limits, setBranding |
setBranding |
account |
me, createOrg, useOrg, listKeys, createKey, revokeKey |
createOrg, useOrg, createKey, revokeKey |
conversations |
list, get, reply, addNote, setStatus, setArchived, setUnread, setSnoozed, setAiStatus, remove, related, stopAi, draftReply |
reply, addNote, remove |
customers |
list, get, update, refreshProfile, removeFact, reachOut, remove |
update when it changes the email, removeFact, reachOut, remove |
knowledge |
tree, search, getDoc, writeDoc, editDoc, removeDoc, moveDoc, retryDoc, syncExternal, createFolder, updateFolder, moveFolder, removeFolder, suggestions, approveSuggestion, dismissSuggestion, crawls, startCrawl, removeCrawl |
removeDoc, removeFolder, approveSuggestion, startCrawl |
signals |
list, get, file, edit, setStatus, setPriority, remove, merge, accept, applyCommit, dismissCommit |
file, setStatus to done, remove, merge, accept, applyCommit |
tasks |
list, get, create, update, setStatus, remove, comments, comment, requests, adopt, decline, settings, updateSettings |
setStatus and adopt to done, remove, decline, updateSettings |
feedback |
submit |
|
board |
settings, posts, post, create, update, setState, setStatus, reply, merge, remove, linkSignal |
Every change but linkSignal |
tools |
approvals, getApproval, approve, deny, runnable, run, test, setEnabled, addEndpoint, removeEndpoint, list, sync, suggestions, markSuggestionBuilt, dismissSuggestion |
approve, deny, run, test, setEnabled, addEndpoint, removeEndpoint, markSuggestionBuilt, dismissSuggestion |
site |
appearance, setAppearance, domain, setDomain, checkDomain, signIn, setSignIn, sections, setSections, articles, publishArticles |
setAppearance, setDomain, setSignIn, setSections, publishArticles |
org |
get, members, invites, createInvite, revokeInvite, resendInvite, update, setMemberRole, removeMember |
createInvite, resendInvite, update, setMemberRole, removeMember |
agent |
get, update |
update |
integrations |
overview, setEmail, setSlack, setDiscord, setLinear, setIssueRepo, removeIssueRepo, setCommitRepo, removeCommitRepo, connectRepo, removeRepo, setPublishedAt |
Every change |
webhooks |
list, create, update, remove |
create, update, remove |
analytics |
overview, conversations, satisfaction, signals, activity, knowledge, billing, describe, explore, records |
|
docs |
search, read |
The assistant
The assistant has a tool for every capability but the five below. Most are written by hand, for friendlier arguments or a trimmed answer, and each stands in for the capabilities it covers, so the model never sees two ways to do one thing. The rest, and any capability added later, get a tool generated from the method’s argument validator and its reference text, so the assistant can use a new capability on the next deploy.
These have no assistant tool, on purpose:
| Capability | Why |
|---|---|
account.me |
The assistant already knows who is asking, and where |
account.createOrg, account.useOrg |
It runs inside an organization that already exists, and cannot rebind a token it is not holding |
account.createKey |
A new key is shown in full once. In a chat it would stay in the transcript |
feedback.submit |
The team files a signal directly with signals.file. submit is for notes from outside the team |
With a conversation open in the inbox, a question to the assistant carries it. The assistant reads that thread, its customer, what it led to and the tools that can run on it before it answers. How that works for a teammate is in Use the assistant on a conversation.
Reply drafts
conversations.draftReply puts a reply in a teammate’s composer instead of
sending it, so it runs without asking. Nothing reaches the customer until a
person sends it.
| Written by | Waits for |
|---|---|
| The assistant, or an OAuth token | The person behind it, on that conversation |
| A Median key | Whoever opens the conversation |
- Writing again replaces the earlier draft from the same writer.
- A reply sent by that person clears their draft.
- The body is held to the same 4,000 characters as a reply. An empty body is refused with
draft_empty.
const [waiting] = await median.conversations.list({ needsHuman: true });
await median.conversations.draftReply({
conversationId: waiting.id,
body: "Thanks for the report. The fix ships tomorrow.",
});
median conversations draft <id> --body "Thanks for the report. The fix ships tomorrow."