call-me-cloud-mcp 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -11
- package/index.ts +20 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
# call-me-cloud-mcp
|
|
2
2
|
|
|
3
|
-
MCP (Model Context Protocol) client that enables Claude to make phone calls via [Call-Me Cloud](https://github.com/riverscornelson/call-me-cloud).
|
|
3
|
+
MCP (Model Context Protocol) client that enables Claude to make phone calls and send WhatsApp messages via [Call-Me Cloud](https://github.com/riverscornelson/call-me-cloud).
|
|
4
4
|
|
|
5
5
|
## What is this?
|
|
6
6
|
|
|
7
|
-
This package lets Claude Code call you on the phone. It connects to a Call-Me Cloud server (which you deploy) and provides MCP tools for initiating and managing voice calls.
|
|
7
|
+
This package lets Claude Code call you on the phone or message you on WhatsApp. It connects to a Call-Me Cloud server (which you deploy) and provides MCP tools for initiating and managing voice calls and text conversations.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
### Via Plugin Marketplace (Recommended)
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
/plugin install call-me-cloud@claude-plugins-official
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Via npx (Manual)
|
|
18
|
+
|
|
19
|
+
Add to your MCP settings (`.mcp.json` or Claude Code config):
|
|
12
20
|
|
|
13
21
|
```json
|
|
14
22
|
{
|
|
15
23
|
"mcpServers": {
|
|
16
24
|
"call-me-cloud": {
|
|
17
25
|
"command": "npx",
|
|
18
|
-
"args": ["-y", "call-me-cloud-mcp"],
|
|
26
|
+
"args": ["-y", "call-me-cloud-mcp@1.3.0"],
|
|
19
27
|
"env": {
|
|
20
28
|
"CALLME_CLOUD_URL": "https://your-server.railway.app",
|
|
21
29
|
"CALLME_API_KEY": "your-api-key"
|
|
@@ -64,25 +72,34 @@ End the call with a closing message.
|
|
|
64
72
|
end_call({ call_id: "call-1", message: "Sounds good, I'll get started. Talk soon!" })
|
|
65
73
|
```
|
|
66
74
|
|
|
75
|
+
### `send_message`
|
|
76
|
+
Send a WhatsApp message and wait for the user's reply.
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
send_message({ message: "The deploy finished. Want me to run the smoke tests?" })
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Returns: `{ messageId, response }` - The message ID and the user's text response.
|
|
83
|
+
|
|
67
84
|
## Requirements
|
|
68
85
|
|
|
69
86
|
- Node.js 18+
|
|
70
87
|
- A deployed [Call-Me Cloud](https://github.com/riverscornelson/call-me-cloud) server
|
|
71
|
-
- Twilio
|
|
88
|
+
- Twilio account for phone calls
|
|
72
89
|
- OpenAI API key (for speech-to-text and text-to-speech)
|
|
73
90
|
|
|
74
91
|
## How It Works
|
|
75
92
|
|
|
76
93
|
```
|
|
77
|
-
Claude Code --> This MCP Client --> Your Cloud Server --> Phone Call
|
|
78
|
-
(stdio) (REST API)
|
|
94
|
+
Claude Code --> This MCP Client --> Your Cloud Server --> Phone Call / WhatsApp
|
|
95
|
+
(stdio) (REST API) (Twilio) (to you)
|
|
79
96
|
```
|
|
80
97
|
|
|
81
|
-
1. Claude decides to call you using one of the MCP tools
|
|
98
|
+
1. Claude decides to call or message you using one of the MCP tools
|
|
82
99
|
2. This client forwards the request to your cloud server
|
|
83
|
-
3. The server initiates a phone call via Twilio
|
|
84
|
-
4. Audio is processed through OpenAI's real-time API
|
|
85
|
-
5. Your response is transcribed and returned to Claude
|
|
100
|
+
3. The server initiates a phone call via Twilio or sends a WhatsApp message
|
|
101
|
+
4. Audio is processed through OpenAI's real-time API (for calls)
|
|
102
|
+
5. Your response is transcribed/received and returned to Claude
|
|
86
103
|
|
|
87
104
|
## Server Setup
|
|
88
105
|
|
package/index.ts
CHANGED
|
@@ -14,6 +14,15 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
|
|
|
14
14
|
const CLOUD_URL = process.env.CALLME_CLOUD_URL;
|
|
15
15
|
const API_KEY = process.env.CALLME_API_KEY;
|
|
16
16
|
|
|
17
|
+
const TOOL_TIMEOUTS: Record<string, number> = {
|
|
18
|
+
initiate_call: 200_000, // user answers phone + speaks
|
|
19
|
+
continue_call: 200_000, // waiting for spoken/WhatsApp response
|
|
20
|
+
send_message: 200_000, // WhatsApp: server waits up to 3 min
|
|
21
|
+
speak_to_user: 30_000, // fire-and-forget
|
|
22
|
+
end_call: 30_000, // quick hangup
|
|
23
|
+
}
|
|
24
|
+
const DEFAULT_TIMEOUT = 30_000
|
|
25
|
+
|
|
17
26
|
if (!CLOUD_URL) {
|
|
18
27
|
console.error('Error: CALLME_CLOUD_URL is required');
|
|
19
28
|
console.error('Set it to your Railway/Render deployment URL');
|
|
@@ -25,7 +34,7 @@ if (!API_KEY) {
|
|
|
25
34
|
process.exit(1);
|
|
26
35
|
}
|
|
27
36
|
|
|
28
|
-
async function apiCall(path: string, body: unknown): Promise<unknown> {
|
|
37
|
+
async function apiCall(path: string, body: unknown, timeoutMs = DEFAULT_TIMEOUT): Promise<unknown> {
|
|
29
38
|
const response = await fetch(`${CLOUD_URL}${path}`, {
|
|
30
39
|
method: 'POST',
|
|
31
40
|
headers: {
|
|
@@ -33,6 +42,7 @@ async function apiCall(path: string, body: unknown): Promise<unknown> {
|
|
|
33
42
|
'Authorization': `Bearer ${API_KEY}`,
|
|
34
43
|
},
|
|
35
44
|
body: JSON.stringify(body),
|
|
45
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
36
46
|
});
|
|
37
47
|
|
|
38
48
|
if (!response.ok) {
|
|
@@ -46,7 +56,9 @@ async function apiCall(path: string, body: unknown): Promise<unknown> {
|
|
|
46
56
|
async function main() {
|
|
47
57
|
// Verify cloud server is reachable (uses unauthenticated /health endpoint)
|
|
48
58
|
try {
|
|
49
|
-
const healthResponse = await fetch(`${CLOUD_URL}/health
|
|
59
|
+
const healthResponse = await fetch(`${CLOUD_URL}/health`, {
|
|
60
|
+
signal: AbortSignal.timeout(5_000),
|
|
61
|
+
});
|
|
50
62
|
if (!healthResponse.ok) {
|
|
51
63
|
throw new Error(`Health check failed: ${healthResponse.status}`);
|
|
52
64
|
}
|
|
@@ -58,7 +70,7 @@ async function main() {
|
|
|
58
70
|
}
|
|
59
71
|
|
|
60
72
|
const mcpServer = new Server(
|
|
61
|
-
{ name: 'call-me-cloud', version: '1.0
|
|
73
|
+
{ name: 'call-me-cloud', version: '1.3.0' },
|
|
62
74
|
{ capabilities: { tools: {} } }
|
|
63
75
|
);
|
|
64
76
|
|
|
@@ -139,7 +151,7 @@ async function main() {
|
|
|
139
151
|
try {
|
|
140
152
|
if (request.params.name === 'initiate_call') {
|
|
141
153
|
const { message } = request.params.arguments as { message: string };
|
|
142
|
-
const result = await apiCall('/api/call', { message }) as {
|
|
154
|
+
const result = await apiCall('/api/call', { message }, TOOL_TIMEOUTS.initiate_call) as {
|
|
143
155
|
callId: string;
|
|
144
156
|
response: string;
|
|
145
157
|
contactMode?: 'voice' | 'whatsapp';
|
|
@@ -179,7 +191,7 @@ async function main() {
|
|
|
179
191
|
|
|
180
192
|
if (request.params.name === 'continue_call') {
|
|
181
193
|
const { call_id, message } = request.params.arguments as { call_id: string; message: string };
|
|
182
|
-
const result = await apiCall(`/api/call/${call_id}/continue`, { message }) as {
|
|
194
|
+
const result = await apiCall(`/api/call/${call_id}/continue`, { message }, TOOL_TIMEOUTS.continue_call) as {
|
|
183
195
|
response: string;
|
|
184
196
|
contactMode?: 'voice' | 'whatsapp';
|
|
185
197
|
whatsappSessionWindow?: {
|
|
@@ -209,7 +221,7 @@ async function main() {
|
|
|
209
221
|
|
|
210
222
|
if (request.params.name === 'speak_to_user') {
|
|
211
223
|
const { call_id, message } = request.params.arguments as { call_id: string; message: string };
|
|
212
|
-
await apiCall(`/api/call/${call_id}/speak`, { message });
|
|
224
|
+
await apiCall(`/api/call/${call_id}/speak`, { message }, TOOL_TIMEOUTS.speak_to_user);
|
|
213
225
|
|
|
214
226
|
return {
|
|
215
227
|
content: [{ type: 'text', text: `Message spoken: "${message}"` }],
|
|
@@ -218,7 +230,7 @@ async function main() {
|
|
|
218
230
|
|
|
219
231
|
if (request.params.name === 'end_call') {
|
|
220
232
|
const { call_id, message } = request.params.arguments as { call_id: string; message: string };
|
|
221
|
-
const result = await apiCall(`/api/call/${call_id}/end`, { message }) as { durationSeconds: number };
|
|
233
|
+
const result = await apiCall(`/api/call/${call_id}/end`, { message }, TOOL_TIMEOUTS.end_call) as { durationSeconds: number };
|
|
222
234
|
|
|
223
235
|
return {
|
|
224
236
|
content: [{ type: 'text', text: `Call ended. Duration: ${result.durationSeconds}s` }],
|
|
@@ -227,7 +239,7 @@ async function main() {
|
|
|
227
239
|
|
|
228
240
|
if (request.params.name === 'send_message') {
|
|
229
241
|
const { message } = request.params.arguments as { message: string };
|
|
230
|
-
const result = await apiCall('/api/message', { message }) as { messageId: string; response: string };
|
|
242
|
+
const result = await apiCall('/api/message', { message }, TOOL_TIMEOUTS.send_message) as { messageId: string; response: string };
|
|
231
243
|
|
|
232
244
|
return {
|
|
233
245
|
content: [{
|