Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@
"command": "bun",
"args": ["run", "--cwd", "${CLAUDE_PLUGIN_ROOT}/server", "start"],
"env": {
"CALLME_PHONE_PROVIDER": "${CALLME_PHONE_PROVIDER:-telnyx}",
"CALLME_PHONE_ACCOUNT_SID": "${CALLME_PHONE_ACCOUNT_SID}",
"CALLME_PHONE_AUTH_TOKEN": "${CALLME_PHONE_AUTH_TOKEN}",
"CALLME_PHONE_NUMBER": "${CALLME_PHONE_NUMBER}",
"CALLME_USER_PHONE_NUMBER": "${CALLME_USER_PHONE_NUMBER}",
"CALLME_OPENAI_API_KEY": "${CALLME_OPENAI_API_KEY}",
"CALLME_TTS_MODEL": "${CALLME_TTS_MODEL:-gpt-4o-mini-tts}",
"CALLME_TTS_VOICE": "${CALLME_TTS_VOICE:-onyx}",
"CALLME_NGROK_AUTHTOKEN": "${CALLME_NGROK_AUTHTOKEN}",
"CALLME_NGROK_DOMAIN": "${CALLME_NGROK_DOMAIN}",
"CALLME_PORT": "${CALLME_PORT:-3333}",
"CALLME_TRANSCRIPT_TIMEOUT_MS": "${CALLME_TRANSCRIPT_TIMEOUT_MS:-180000}",
"CALLME_STT_SILENCE_DURATION_MS": "${CALLME_STT_SILENCE_DURATION_MS:-800}"
"CALLME_STT_SILENCE_DURATION_MS": "${CALLME_STT_SILENCE_DURATION_MS:-800}",
"CALLME_INBOUND_GREETING": "${CALLME_INBOUND_GREETING:-Hello, this is Claude. How can I help you?}"
}
}
},
Expand All @@ -33,8 +37,18 @@
{
"hooks": [
{
"type": "prompt",
"prompt": "SILENTLY evaluate if you should call the user. ONLY use initiate_call if you completed significant work and need to discuss next steps, or are genuinely blocked. Do NOT output any text - either call or do nothing."
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/inbound-call-hook.sh"
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/check-pending-call.sh"
}
]
}
Expand Down
41 changes: 41 additions & 0 deletions hooks/check-pending-call.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash
# Check for pending inbound calls on user prompt submit
# Injects a notice if there's an active call waiting

PENDING_FILE="/tmp/callme-pending-inbound.json"

# Check if pending call file exists
if [[ ! -f "$PENDING_FILE" ]]; then
exit 0
fi

# Read the pending call info
CALL_INFO=$(cat "$PENDING_FILE")
CALL_ID=$(echo "$CALL_INFO" | jq -r '.callId')
FROM=$(echo "$CALL_INFO" | jq -r '.from')
TRANSCRIPT=$(echo "$CALL_INFO" | jq -r '.transcript')
TIMESTAMP=$(echo "$CALL_INFO" | jq -r '.timestamp')

# Check if the call is stale (older than 5 minutes = 300000ms)
NOW=$(date +%s)
NOW_MS=$((NOW * 1000))
AGE=$((NOW_MS - TIMESTAMP))
if [[ $AGE -gt 300000 ]]; then
rm -f "$PENDING_FILE"
exit 0
fi

# Don't remove the file - let Stop hook handle cleanup after Claude responds
# Just prepend context to the user's message

cat << EOF
[URGENT - ACTIVE PHONE CALL]
Someone is on the phone waiting for you to respond!

Call ID: $CALL_ID
Caller: $FROM
They said: "$TRANSCRIPT"

IMPORTANT: Before doing anything else, use continue_call with call_id="$CALL_ID" to respond to them!
---
EOF
42 changes: 42 additions & 0 deletions hooks/inbound-call-hook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/bin/bash
# Inbound Call Stop Hook
# Checks for pending inbound calls and prompts Claude to respond

PENDING_FILE="/tmp/callme-pending-inbound.json"

# Check if pending call file exists
if [[ ! -f "$PENDING_FILE" ]]; then
exit 0
fi

# Read the pending call info
CALL_INFO=$(cat "$PENDING_FILE")
CALL_ID=$(echo "$CALL_INFO" | jq -r '.callId')
FROM=$(echo "$CALL_INFO" | jq -r '.from')
TRANSCRIPT=$(echo "$CALL_INFO" | jq -r '.transcript')
TIMESTAMP=$(echo "$CALL_INFO" | jq -r '.timestamp')

# Check if the call is stale (older than 5 minutes)
NOW=$(date +%s)
NOW_MS=$((NOW * 1000))
AGE=$((NOW_MS - TIMESTAMP))
if [[ $AGE -gt 300000 ]]; then
# Call is stale, remove the file
rm -f "$PENDING_FILE"
exit 0
fi

# Remove the pending file so we don't prompt again
rm -f "$PENDING_FILE"

# Build the reason message
REASON="URGENT: There is an active inbound phone call waiting for your response!

Call ID: ${CALL_ID}
Caller: ${FROM}
They said: \"${TRANSCRIPT}\"

Use continue_call with call_id=\"${CALL_ID}\" to respond to them, or end_call to hang up."

# Output JSON to inject a prompt about the inbound call
jq -n --arg reason "$REASON" '{"decision": "block", "reason": $reason}'
19 changes: 18 additions & 1 deletion server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,26 @@ async function main() {
// Create stdio MCP server
const mcpServer = new Server(
{ name: 'callme', version: '3.0.0' },
{ capabilities: { tools: {} } }
{ capabilities: { tools: {}, logging: {} } }
);

// Wire up inbound call notifications to MCP
callManager.setInboundCallHandler((callId, from, transcript) => {
mcpServer.notification({
method: 'notifications/message',
params: {
level: 'info',
data: {
type: 'inbound_call',
call_id: callId,
from: from,
transcript: transcript
},
message: `Incoming call from ${from}!\n\nUser said: "${transcript}"\n\nUse continue_call with call_id="${callId}" to respond, or end_call to hang up.`
}
});
});

// List available tools
mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
return {
Expand Down
Loading