Documentation

Askance for agents and the people who build them

Askance is a standard remote MCP server. There is no SDK, no package and no local process — one entry in your client's MCP config connects it. Everything your agent can do is listed below, along with what happens while it waits for a human.

MCP endpointhttps://mcp.askance.app/mcp

Transportstreamable HTTP

AuthAuthorization: Bearer <key>

Connect your client

Project-scoped MCP config, plus a Skill so the agent reaches for Askance on its own.

  1. Fastest: paste this to your agent

    Your agent can do the whole setup itself — and unlike a copied snippet it can see which OS and client it is in, so it picks a place for the key that actually survives a restart. Paste this, with your key from Settings, and let it work. The manual steps below are the fallback.

    Set up Askance in this project.
    
    Fetch https://www.askance.app/install.md and follow it exactly, for whichever
    client and OS you are running in.
    
    My key is: <paste your key here>
    
    Keep the connection specific to THIS project, and check for an existing Askance
    connection first — if there is already one (for a different Askance project), name
    this one after the project rather than reusing "Askance".
    
    Store the key where it persists across restarts, and never in a file that gets
    committed — check .gitignore before writing anything, and do not echo the key
    back to me.
  2. Or add it yourself: one command, no environment variable

    Defaults to --scope local: recorded against THIS project directory and private to you, so the key persists across restarts with no environment variable to set and nothing secret can be committed. Run `claude mcp list` first — if an Askance connection already exists for a different Askance project, name this one after the project (Askance-YourProject) so calls and errors say which one they came from. Renaming changes the tool ids to mcp__Askance_YourProject__*, so only do it when there is genuinely more than one.

    claude mcp add --transport http Askance https://mcp.askance.app/mcp   --header "Authorization: Bearer ask_..."
  3. Only if your TEAM shares the config: .mcp.json

    Commit this at the root of your repo. Every teammate who opens the project gets the same connection, and the key stays in their environment rather than in the file. Name the server after the project when it is shared like this — a teammate seeing a bare "askance" cannot tell which Askance project it reaches, and anyone who works on two of your repos ends up with two connections answering to the same name.

    .mcp.json
    {
      "mcpServers": {
        "askance": {
          "type": "http",
          "url": "https://mcp.askance.app/mcp",
          "headers": {
            "Authorization": "Bearer ${ASKANCE_KEY}"
          }
        }
      }
    }
  4. Add a Skill so the agent knows when to ask

    Connecting the server gives Claude Code tools it can call, but nothing that prompts it to. A Skill is model-invoked — Claude reads the description and reaches for it when the situation matches — which is what turns Askance from an available tool into one that actually gets used.

    .claude/skills/askance/SKILL.md
    ---
    name: askance
    description: Ask a human subject-matter expert when a question cannot be answered from the codebase — business rules, product decisions, integration details. Use whenever you would otherwise guess or stall.
    ---
    
    When you hit a question you cannot answer from the codebase,
    the docs, or the conversation — a business rule, a product decision, an
    integration detail, anything held by a person rather than written down —
    do NOT guess and do NOT stop and wait for the user.
    
    1. Call Askance's `search_records` first. It may already be answered.
    2. If not, call `ask_question` with the full context, the options you can
       see, and the one you recommend.
    3. Carry on with other work immediately — do not sit and wait for the answer.
       Answers can take hours or days. If the question genuinely gates everything
       else, say so in the question so a human knows it is urgent.
    4. Call `check_questions` at the start of a session and between tasks to
       pick up answers that have landed.
  5. Set your key so it persists

    Your key comes from Settings once you have signed in. Set it so it SURVIVES A RESTART — a bare `export` in a terminal lasts only for that terminal, and an editor you launched from a Start menu or Dock never sees it at all. That is the single most common reason a connection works once and is gone the next day.

    # macOS / Linux — append to ~/.zshrc (or ~/.bashrc), then open a NEW terminal
    echo 'export ASKANCE_KEY="ask_..."' >> ~/.zshrc
    
    # Windows PowerShell — persists for your user account
    [Environment]::SetEnvironmentVariable('ASKANCE_KEY', 'ask_...', 'User')
    
    # Then FULLY restart the app (not just the window) and check it resolved:
    #   macOS/Linux:  printenv ASKANCE_KEY
    #   PowerShell:   $env:ASKANCE_KEY
    
    # in Claude Code, after restarting:
    /mcp          # askance should be listed as connected, not failed

Your key

Every request carries Authorization: Bearer <key>. Keys come from the web app once you have signed in — a personal key from Settings, or a project-scoped service account from the Members page when the caller should be your CI or a shared agent rather than you.

Environment variables are client-dependent

The snippets above reference ASKANCE_KEY rather than a literal key, because the key should not end up in a file you commit. How that reference is resolved is up to the client, and they do not agree:

  • Claude Code expands $${ASKANCE_KEY} from your shell environment.
  • Cursor uses its own $${env:ASKANCE_KEY} syntax inside url and headers.
  • Codex takes the NAME of the variable (bearer_token_env_var) and builds the header itself.
  • Anything else: check whether your client expands variables at all. Several do not. If yours does not, paste the literal key — and make sure the file it lives in is not committed. git check-ignore -v <file> tells you before it is too late.

A client that does not expand the variable sends the literal string $${ASKANCE_KEY} as the key, and every call fails authentication with no obvious cause. If your first call returns 401, check this first.

Set the variable so it survives a restart

This is the most common way a working setup quietly stops working. A bare export ASKANCE_KEY=... lasts only for the terminal you typed it in, and an editor launched from a Start menu, Dock or desktop icon never reads your shell profile at all — so the config file looks perfect, the variable resolves to nothing, and every call gets Bearer with no key after it. Set it for your user account instead, then fully restart the app:

  • macOS / Linux: append export ASKANCE_KEY="ask_..." to ~/.zshrc or ~/.bashrc, then open a new terminal. If you launch your editor from an icon rather than that terminal, it may still not see it — launch it from the terminal, or set the variable at the OS level.
  • Windows: export is not a command in PowerShell, so this step is silently skipped and nothing is set. Use [Environment]::SetEnvironmentVariable('ASKANCE_KEY', 'ask_...', 'User') instead, which persists for your account.

Then check it actually resolved, from the same account that runs the client: printenv ASKANCE_KEY (macOS/Linux) or $env:ASKANCE_KEY (PowerShell). An empty result is the whole bug — the config is fine and the key was never there.

Personal keys and service accounts

A personal key (Settings) authenticates as you — everything you can do, it can do. A service account (a project's Members page) is a non-human member of one project, and it is what CI and shared agents should use. Two differences matter when you are deciding:

  • A service account is scoped to exactly one project, always. It cannot reach a second one, and it cannot create one. If you work across several projects, that is several service accounts — which is the point: a leaked key exposes one project.
  • It can hold several keys, one per app. Issue a named key for GitHub Actions and another for your laptop, and revoke the one that leaked without breaking the other. Regenerating a key revokes the old one immediately — there is no grace period, because you regenerate when something has leaked.
  • Keys are never re-displayed. They are stored as one-way hashes, so nobody — including us — can recover one after the moment it is created. Lost it? Issue another; that is cheaper and safer than a system that could hand it back.

What a service account is allowed to do

Every service account carries a role, set by a project owner on the Members page. It applies to that account's very next call — no key reissue — so an owner can downgrade something that is misbehaving immediately. If a tool returns a permission error naming a role, this is why.

RoleCan
ReadOnlySearch the record and read: search_records, list_*, get_*, check_*. Writes nothing.
AskdefaultEverything above, plus ask_question and set_question_escalation.
ContributeEverything above, plus propose_direction, report_correction, comment_* and update_project_background.
AdministerEverything above, plus maintaining the project's topics — create_topic, rename_topic, delete_topic and set_member_topics.

Grant Administer deliberately. Topic assignments decide who gets notified about a question, so an account that can rewrite them can decide that nobody hears about one — with every call still returning success. It is off by default and an owner sets it explicitly; an existing account never gains it by upgrade.

No role reaches credentials, membership or billing — issuing or revoking keys, changing any account's role (including its own), inviting or removing members, billing, and deleting or transferring the project stay with a human owner whatever an account's role is. Every one of those either mints new credentials or widens the caller's own authority, which would make a leaked key self-perpetuating: being able to downgrade a misbehaving agent is worth nothing if the agent can undo the downgrade.

How waiting works

A human is not a function call. Askance is built around that, and the mechanics matter more here than they would for an ordinary API — an agent that blocks on a person is a broken agent.

Does my agent block?
No. ask_question returns immediately with a question id. Nothing waits, and there is no long-poll or open connection. Your agent records the assumption it is proceeding under and carries on.
How long can an answer take?
As long as the human takes — minutes, or days. There is no timeout and no expiry: a question stays Open until somebody answers it, or the asker closes or cancels it. It is never silently discarded.
How do I get the answer?
Poll. check_questions returns the batched status of everything you have asked in a project, with the answer inline where one exists. Call it at natural checkpoints — session start, between tasks — not in a tight loop. get_answer fetches one answer in full.
What comes back if nobody has answered yet?
The question, with status Open and a null answer. That is a normal result, not an error: an unanswered question is the expected state for most of a question's life.
What if nobody ever answers?
Set a backup with set_question_escalation: nominate another project member and, optionally, when to escalate. Left unset, the window is 72 hours from the moment you configure it. When it passes with the question still open, the backup answerer is notified. Without an escalation configured, an unanswered question simply stays open — which is why it is worth setting one for anything that actually blocks a decision.
Can a person hand it to someone else?
Yes, from the web app. The question moves to whoever they pass it to and leaves their own list. Your agent sees no change beyond the answer eventually arriving from a different name.

Tool reference

36 tools, grouped by the job they do. Names, descriptions and parameters are taken from the server itself, so this page and the tools your agent sees cannot disagree.

Getting set up

setup_askance

Run this once, right after connecting Askance, or whenever the user asks to set up, configure or finish installing Askance. Returns a setup briefing for THIS caller: which projects the credential can reach, the standing instructions to save into the repo so future sessions know when to escalate to a human, and where each client keeps them. Confirm the choices with the user before writing any file.

Returns string — a markdown setup briefing naming the projects this credential can reach

Asking and answering

ask_question

Escalate to a human expert when the blocker is JUDGEMENT, not information: a business rule not derivable from the code; a decision expensive to reverse (schema on live data, pricing, anything customer-visible); a trade-off with no technically-correct answer; or a conflict between what the code does and what published copy promises. Do NOT use it for what reading the code, docs or git history would answer — that is research, and doing it is your job — nor to avoid a decision that is legitimately yours. Call search_records first; the permanent record may already answer it. NON-BLOCKING: returns immediately, answers may take days. Record the assumption you are proceeding under, keep working, and collect the answer later with check_questions. Never wait for a reply.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id
titlerequiredstringshort question title
contextrequiredstringmarkdown context: what was being attempted, what's known, why it's blocked
optionsobject[] — {text, rationale, is_recommended}optional suggested answer options, at most one flagged is_recommended
targetTopicTagIdsstring[] (uuids)optional TopicTag ids (from list_answerers) to route the question to
targetAnswererUserIdsstring[] (uuids)optional specific project-member user ids to target directly
selectionModestring — SingleChoice | MultipleChoice"SingleChoice" (default) if the options are alternatives and the human picks ONE, or "MultipleChoice" if they may pick any number. A free-text answer is always possible in both modes, so no "Other" option is needed.

Returns {question_id, routed_to[], answerer_count, routed_by_default} — routed_by_default is true when nothing you targeted resolved to a human and the question was defaulted to the project owner

update_question

Revise a question you asked that is still Open — better wording, more context, different options. Use this instead of cancelling and re-asking, which leaves the project holding near-duplicates. Anyone already notified is told it changed. Your own question, or anyone else's if you are the project owner or a service account with Administer access. Refused once answered — at that point nobody may edit it, including the owner, because the answer refers to the wording as it was.

ParameterTypeNotes
questionIdrequiredstring (uuid)question id
titlestringnew title, or omit to leave unchanged
contextstringnew markdown context, or omit to leave unchanged
optionsobject[] — {text, rationale, is_recommended}replacement option set (REPLACES the existing options), or omit to leave them alone
selectionModestring — SingleChoice | MultipleChoiceor omit to leave unchanged

Returns QuestionDetail

answer_question

Record the answer to a question YOU asked, when you worked it out yourself before a human replied — so the finding lands in the permanent record instead of being lost. The record shows it was answered by this service account, not by a human expert. With Administer access you may also answer a question asked by another identity: how an owner records an answer given out of band, or recovers questions whose asking credential was retired. Do NOT use it to answer on a human's behalf.

ParameterTypeNotes
questionIdrequiredstring (uuid)question id
bodyrequiredstringthe answer, in your own words
chosenOptionIdsstring[] (uuids)optional ids of the option(s) this answer picks, from get_question

Returns AnswerSummary

list_questions

Every question in the project, whoever asked it — unlike check_questions, which only returns your own. Use it to orient yourself on a project you have not worked in before, or after a credential change: questions asked by a previous agent are invisible to check_questions but are right here. status="Open" for what is outstanding; updatedSince=<newest last_changed_at_utc you saw> between tasks returns only what moved.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id
statusstringoptional status filter: Open|Answered|Closed|Cancelled — omit for all
updatedSincestring (ISO 8601 UTC)only questions changed at or after this instant
includeAnswersbooleaninclude each answered question's answer body (default false)
limitnumbermax rows, 1-200 (default 50)

Returns QuestionListItem[] — newest-changed first

get_question

One question's own shape — title, context, status, and its options with their ids and whether it takes one pick or several (selection_mode). Use it to read back a question you asked, or to get option ids for answer_question.

ParameterTypeNotes
questionIdrequiredstring (uuid)question id

Returns QuestionDetail

cancel_question

Withdraw a question you asked that is still Open and is no longer needed. It stops chasing anyone but stays in the record as asked-and-withdrawn, which tells the next agent it was already considered. Your own question, or anyone else's if you are the project owner or a service account with Administer access. Only valid while Open.

ParameterTypeNotes
questionIdrequiredstring (uuid)question id

Returns QuestionStatusChange

delete_question

Remove a question you asked that is still Open and should never have been asked — a duplicate, one aimed at the wrong project, one whose context came out malformed. It disappears from search, listings and everyone's queue. Your own question, or anyone's if you are the project owner or a service account with Administer access. Refused once answered: an answered question is a person's work and stays in the record, for everyone.

ParameterTypeNotes
questionIdrequiredstring (uuid)question id

Returns QuestionStatusChange

close_question

Mark a question as Closed once its answer has been consumed. Your own, or anyone else's if you are the project owner or a service account with Administer access — otherwise a question asked by a retired credential would sit in Answered for ever with nobody able to close it. Only valid on an Answered question. Courtesy rather than bookkeeping: it tells the expert their answer landed and was used.

ParameterTypeNotes
questionIdrequiredstring (uuid)question id

Returns QuestionStatusChange

check_questions

The polling tool (spec §5.1/§5.2): batched statuses (+ answers where available) for questions the caller asked in a project, in one call. Call this at natural checkpoints (session start, between tasks) — do not tight-loop it; continue other work while questions are open.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id
statusstringoptional status filter: Open|Answered|Closed|Cancelled — omit to return all of the caller's questions

Returns QuestionStatus[]

check_assigned_questions

The QuestionAnswerer counterpart to check_questions: batched statuses (+ answers where available) for questions ROUTED TO the caller in a project — targeted directly or via a matching topic (spec §4.2) — rather than questions the caller asked. Use this if you're acting on behalf of an SME who has been asked to answer, not to ask.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id
statusstringoptional status filter: Open|Answered|Closed|Cancelled — omit to return all questions assigned to the caller

Returns QuestionStatus[]

get_answer

Full answer for one question, including any extracted attachment text. Returns null when the question hasn't been answered yet — use check_questions to know when to call this.

ParameterTypeNotes
questionIdrequiredstring (uuid)question id

Returns AnswerDetail

set_question_escalation

Configure (or re-arm) auto-escalation for a still-open question (D7): if unanswered by escalate_after_utc, the question is escalated to backup_answerer_user_id (added as an answerer and notified, alongside the original asker). Caller must be the question's asker or the project owner. backup_answerer_user_id must be an existing project member (see list_answerers). Omit escalate_after_utc to default to 72 hours from now. Calling this again re-arms escalation, including clearing a previous firing.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id
questionIdrequiredstring (uuid)question id
backupAnswererUserIdrequiredstring (uuid)project-member user id to escalate to if unanswered in time
escalateAfterUtcstring (ISO 8601 UTC)optional UTC deadline after which the question escalates if still unanswered — omit for +72h from now

Returns QuestionEscalation

search_records

Search the project's permanent Q&A record — call this BEFORE ask_question, the record may already answer it (spec §4.6). Substring search over question titles/context and answer bodies.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id
queryrequiredstringsearch text

Returns SearchResult[]

Finding the right human

list_answerers

List a project's members and the topic tags each can answer — use this to route a question by topic or to target specific answerers before calling ask_question.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id

Returns Answerer[]

list_topics

List a project's topics with their ids. list_answerers shows which topics each member holds but only by name — use this to get the ids ask_question's targetTopicTagIds needs.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id

Returns TopicTag[]

create_topic

Add a topic to a project. Names are unique per project, case-insensitively; re-creating a deleted topic revives it (its old member assignments stay off). A new topic routes nothing until someone is assigned it. OWNER-ONLY: refused for service-account credentials — ask your human to add the topic.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id
namerequiredstringtopic name, max 100 chars

Returns TopicTag

rename_topic

Rename a topic, keeping its id — member assignments and past questions tagged with it follow the rename. OWNER-ONLY: refused for service-account credentials, since renaming changes the meaning of a topic people are already assigned to.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id
topicTagIdrequiredstring (uuid)topic id, from list_topics
namerequiredstringnew topic name, max 100 chars

Returns TopicTag

delete_topic

Delete a topic and every member's assignment to it; questions already tagged with it keep their tag. Returns the project's remaining topics. OWNER-ONLY: refused for service-account credentials, since this removes a route to a human.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id
topicTagIdrequiredstring (uuid)topic id, from list_topics

Returns TopicTag[]

set_member_topics

Replace which topics a member can answer — the full set, so omitting one removes it. Use list_answerers for the member id and list_topics for topic ids. OWNER-ONLY: refused for service-account credentials, because these assignments decide who gets notified about a question.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id
projectMemberIdrequiredstring (uuid)project member id, from list_answerers
topicTagIdsrequiredstring[] (uuids)the member's complete new topic id set — an empty list removes all of them, which stops this person being notified by topic at all

Returns IReadOnlyList<Guid>

Projects

list_projects

List projects the caller is a member of.

Returns Project[]

create_project

Create a new project owned by the caller (first-use auto-creation path, spec §7). The caller becomes the project's owner and first member.

ParameterTypeNotes
namerequiredstringproject name
descriptionstringoptional description
codePrefixstringoptional short uppercase feature-numbering prefix (e.g. 'ASK'); derived from the name when omitted

Returns Project

get_project_plan

Which organisation pays for a project and what plan it is on, plus how much of this month's answered-question allowance is used. Call this when a write is refused for quota reasons — it turns "the limit hit" into something you can explain and act on, rather than retrying into the same wall. READ-ONLY: there is no tool to change a plan, add members or touch a payment method, and there will not be — those are decisions for the humans who own the account.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id

Returns ProjectPlan

get_project

Full detail for one project, including its long-form background (up to 20,000 chars) — unlike list_projects, which omits background to stay light. READ THIS BEFORE calling update_project_background: there is no versioning in v1, so you must see the current text to extend it correctly rather than guessing at what's already there.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id

Returns ProjectDetail

update_project_background

Set the project's long-form background (architecture notes, domain context, links, anything worth knowing before engaging with the project) — shared documentation any project member, including you, may maintain. ALWAYS call get_project FIRST and EXTEND the existing text rather than replacing it wholesale: there is no versioning in v1, so overwriting loses whatever was there before, including anything a human or another agent wrote. Replace the entire field's text (this is not an append operation) — read, merge in your addition, then write the combined text back.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id
backgroundrequiredstringthe full new background text — read the current value via get_project and extend it, don't erase it

Returns ProjectDetail

list_project_phases

List a project's phase labels (spec §12.1). Phase numbers surfaced elsewhere (list_directions/propose_direction/update_direction's phase field) don't always have a label — call this to resolve one for context before reasoning about \"phase 2\" etc.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id

Returns ProjectPhase[]

upsert_project_phase

Create or rename a phase label (spec §12.1) — OWNER-SCOPED, same as update_direction: only the project owner may call this successfully. Pass phase_id to rename/renumber an existing label; omit it to create a new one for phase_number (fails if that phase number already has a label in this project).

ParameterTypeNotes
projectIdrequiredstring (uuid)project id
phaseNumberrequirednumberphase (work-package) number
labelrequiredstringthe phase's label, e.g. \"MVP\"
phaseIdstring (uuid)id of an existing phase label to rename/renumber — omit to create a new one

Returns ProjectPhase

Directions — what to build

list_directions

List the Directions for a project — the features and changes to build (spec §12.1), optionally filtered by status or phase. Each result includes its net vote score and vote/feedback counts. Downvotes or a negative net score are a SIGNAL, not a command: don't silently demote or delete the Direction — ask a structured follow-up Question via ask_question (reference the Direction's code, e.g. \"ASK-12\", in the question) to understand why before changing anything.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id
statusstringoptional status filter: ForConsideration|Confirmed|Planned|Implementing|Completed|Declined
phasenumberoptional phase (work-package number) filter

Returns DirectionSummary[]

propose_direction

Propose a new Direction — a feature or change to build (spec §12.1). PROPOSE, DON'T DECREE (spec §12.3): the Direction always arrives as ForConsideration regardless of how confident you are — never invent a different starting status. If you have a view on sequencing, say so via suggested_phase/ suggested_priority (a recommendation the owner can accept or override), not by asserting it as fact. Put your reasoning in the description; the owner confirms via the UX or a follow-up Question.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id
headingrequiredstringshort heading for the Direction
descriptionstringmarkdown description — include your reasoning/rationale here, especially for AI-proposed Directions
suggestedPhasenumberoptional recommended phase (work-package number) — a suggestion, not a decision
suggestedPrioritynumberoptional recommended priority within the phase — a suggestion, not a decision

Returns DirectionSummary

get_direction

Full detail for one Direction (spec §12): net vote score, your own vote (if any), the full feedback stream, and any linked Questions. Call this before update_direction/comment_direction so you're reacting to the latest votes/feedback, not stale state.

ParameterTypeNotes
directionIdrequiredstring (uuid)feature id

Returns DirectionDetail

update_direction

Change a Direction's status, phase, priority, and/or description (spec §12.1). OWNER-SCOPED (D22): only the project owner may call this successfully today; a member's call fails with a forbidden error. KEEP STATUSES CURRENT AS WORK PROCEEDS (spec §12.3): as you implement a confirmed/planned direction, advance its status (Planned → Implementing → Completed) rather than leaving it stale, and pair each status change with a progress note via comment_direction. Valid transitions: ForConsideration→Confirmed, Confirmed→Planned, Planned→Implementing, Implementing→Completed; Declined is reachable from any of those four. Skipping a step (e.g. ForConsideration straight to Completed) is rejected. Fields you omit are left unchanged.

ParameterTypeNotes
directionIdrequiredstring (uuid)feature id
statusstringoptional new status: ForConsideration|Confirmed|Planned|Implementing|Completed|Declined — omit to leave the status unchanged
phasenumberoptional new phase (work-package number) — omit to leave unchanged
prioritynumberoptional new priority within the phase — omit to leave unchanged
descriptionstringoptional new markdown description — omit to leave unchanged

Returns DirectionSummary

comment_direction

Post to a Direction's feedback stream (spec §12.2/§12.3) — use this both to respond to human votes/feedback (e.g. explain a status change, ask for clarification on a downvote instead of silently reacting to it) and to post your own progress notes as you implement a direction. Any project member, human or AI, may comment — this is not owner-scoped like update_direction.

ParameterTypeNotes
directionIdrequiredstring (uuid)feature id
textrequiredstringcomment/progress-note text

Returns string (uuid) — the new question id

Corrections — what the build got wrong

report_correction

Report a Correction — something the build got wrong, that needs putting right. Report it when something is BROKEN — wrong behaviour, an error, data that looks incorrect. For something merely missing or desirable, use propose_direction instead. Severity and impact are your PROPOSAL: pick them honestly, because only the project owner can change them afterwards and they set the priority band everyone works to. Do not report the same defect twice — call list_corrections first.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id
headingrequiredstringshort summary of what is broken
descriptionstringmarkdown: what happened, what you expected, and anything you already ruled out
reproductionStepsstringmarkdown: the shortest reliable way to make it happen again
severityCorrectionSeverityhow BADLY it behaves, independent of how many are affected: Critical (data loss, security, or work cannot proceed) | High (a core path is broken with no workaround) | Medium (broken but there is a workaround) | Low (cosmetic or a minor annoyance)
impactCorrectionImpacthow MANY it reaches, independent of how bad it is: AllUsers | Many | Some | Single. Judge this from evidence, not assumption — if you only know it happened to you, that is Single.

Returns CorrectionSummary

list_corrections

Open Corrections for a project, most urgent first. Priority is DERIVED from severity x impact — use the returned band rather than re-deriving your own ordering. Call this before report_correction so you do not file a duplicate, and when deciding what to work on next.

ParameterTypeNotes
projectIdrequiredstring (uuid)project id
statusstringoptional status filter: Reported|Acknowledged|InProgress|Resolved|Declined|Duplicate

Returns CorrectionSummary[]

get_correction

Full detail for one Correction, including reproduction steps and the whole comment stream. Call this before commenting so you do not repeat something already said.

ParameterTypeNotes
correctionIdrequiredstring (uuid)issue id

Returns CorrectionDetail

comment_correction

Add a note to a Correction — extra detail, a reproduction you found, or what you changed. Any project member may comment. Use this rather than silently changing course: if you believe the severity is wrong, say so here, since only the owner can change it.

ParameterTypeNotes
correctionIdrequiredstring (uuid)issue id
bodyrequiredstringmarkdown note

Returns CorrectionComment

A worked example

The whole loop: find who can answer, ask, keep working, collect later. Tool calls are shown as the arguments your agent supplies.

// 1. Who can answer, and what topics exist to route by
list_answerers  { projectId: "69f94b1d-..." }
list_topics     { projectId: "69f94b1d-..." }

// 2. Ask. Returns immediately with an id — nothing blocks.
ask_question {
  projectId: "69f94b1d-...",
  title: "Should deleting a customer also delete their invoices?",
  context: "Building the delete endpoint. The schema cascades, but NZ tax rules\nlook like they require invoices to be retained for 7 years.",
  options: [
    { text: "Soft-delete the customer, keep invoices", rationale: "Meets retention, keeps the UI clean", is_recommended: true },
    { text: "Hard-delete both", rationale: "Simplest, but likely unlawful" }
  ],
  targetTopicTagIds: ["2c315aef-..."]
}
// -> "caf24a5e-54a9-44ac-bf23-34aed053e19f"

// 3. Carry on with other work. Later, at a checkpoint:
check_questions { projectId: "69f94b1d-...", status: "Answered" }
// -> [{ question_id: "caf24a5e-...", status: "Answered",
//       answer: { body: "Soft-delete the customer, keep invoices", ... } }]

Limits and errors

  • Rate limit: 60 requests per minute per credential, on a fixed window, with no queue. Over it you get 429; retry after the window rolls.
  • Plan quota: questions per month and seats depend on your plan — see pricing. A quota refusal is a 4xx naming the limit, not a silent drop.
  • Errors come back as MCP tool errors carrying the reason. 401 means the key is missing, wrong, or an unexpanded $${ASKANCE_KEY}. 403 means the caller is authenticated but not entitled — most often not a member of that project, or an owner-only action attempted with a service account.
  • Retries are safe for reads. ask_question is not idempotent, so a blind retry after a timeout can create a second question — call check_questions first and look for the one you may already have asked.

Next: invite whoever knows

Askance does nothing until someone can answer

Connecting the server is your half. The other half is one person who holds the answers your agent keeps guessing at. There is a page written for them rather than for you, with no setup in it.