A blocked Claude Code session phones you. You say the answer out loud. The session unblocks and continues.
A stuck session costs the forty minutes it sits idle, not the eight seconds the decision takes. One-way alerts don't fix that because you still have to walk back and type. This is a two-way channel: one MCP tool, call_me(context, question, options), that places a real outbound call through CALL-E, blocks until you answer, and returns your spoken decision to the session.
- Node 24 or newer.
- A CALL-E account. New accounts get 20 free calls. Create an API key at https://dashboard.heycall-e.com/account/api-keys.
- A phone number CALL-E can dial. Coverage is 40+ countries; see the regions table. India (+91) is on the international line.
No code of ours involved. This uses CALL-E's own CLI:
export CALLE_API_KEY=calle_...
npx @call-e/calle@latest calls create \
--phone "+91XXXXXXXXXX" \
--task "Call this person, say 'this is Fleet Dispatch, testing', and ask if they can hear you clearly." \
--wait --jsonIf your phone rings, everything else will work.
git clone git@github.com:Yashodeeps/fleet-dispatch.git ~/fleet-dispatch
cd ~/fleet-dispatch && npm install && npm test
cat > .env <<'EOF'
CALLE_API_KEY=calle_...
FLEET_DISPATCH_PHONE=+91XXXXXXXXXX
FLEET_DISPATCH_REGION=IN
EOF
claude mcp add --scope user --transport stdio fleet-dispatch -- node ~/fleet-dispatch/src/server.tsThe server reads .env next to its source. Variables set in the shell or via claude mcp add --env take precedence. .env is gitignored. User scope means every session on this machine gets the tool.
The Skill holds the policy for when a session should dial and when it should not.
mkdir -p ~/.claude/skills && cp -r ~/fleet-dispatch/skill ~/.claude/skills/fleet-dispatchIn any claude session:
Call me and ask whether to drop the users table or skip the migration.
Your phone rings. Say "skip it". The tool returns:
{
"answered": true,
"decision": "Skip the migration",
"chosen_option": "Skip the migration",
"reasoning": null,
"reason": "answered",
"call_id": "call_...",
"summary": "...",
"transcript": [{ "speaker": "bot", "text": "..." }, { "speaker": "user", "text": "..." }]
}Set FLEET_DISPATCH_DRY_RUN=1 instead of CALLE_API_KEY and the server answers with the first option after a moment, without dialing. Useful for wiring up a new machine or recording a demo.
| Variable | Required | Meaning |
|---|---|---|
CALLE_API_KEY |
yes, unless dry run | CALL-E API key |
FLEET_DISPATCH_PHONE |
yes | Your number in E.164, the only number this server will ever dial |
FLEET_DISPATCH_REGION |
no | Country code hint for routing, e.g. IN |
FLEET_DISPATCH_LOCALE |
no | BCP 47 locale hint, e.g. en-IN |
FLEET_DISPATCH_DRY_RUN |
no | 1 to never dial |
call_me(context, question, options?, default?, session?, timeout_seconds?)
- Blocks up to
timeout_seconds(default 300, max 900). Claude Code's MCP tool timeout defaults to many hours, so nothing else to configure. answered: truemeans you gave and confirmed a decision.reason: holdmeans you told it to wait. The default is never applied over an explicit hold.reason: timeoutorfailed:<code>returnsdefaultif one was given, otherwisenullso the session halts.- The phone number comes from the environment, never from the model. The model cannot choose whom to call.
- Every non-dry-run tool call places one real phone call to
FLEET_DISPATCH_PHONEand consumes CALL-E credit. - There is no retry. A missed call returns the fallback and the Skill tells the session not to call again for the same question.
- Cancel by hanging up. The call ends and the session gets
reason: unclearorholddepending on what was said.