call-me-cloud-mcp 1.1.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 +45 -7
- 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
|
|
|
@@ -116,6 +128,20 @@ async function main() {
|
|
|
116
128
|
required: ['call_id', 'message'],
|
|
117
129
|
},
|
|
118
130
|
},
|
|
131
|
+
{
|
|
132
|
+
name: 'send_message',
|
|
133
|
+
description: 'Send a WhatsApp message to the user. Use when a phone call is not appropriate or when the user prefers text. Waits for user response.',
|
|
134
|
+
inputSchema: {
|
|
135
|
+
type: 'object',
|
|
136
|
+
properties: {
|
|
137
|
+
message: {
|
|
138
|
+
type: 'string',
|
|
139
|
+
description: 'The message to send via WhatsApp.',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
required: ['message'],
|
|
143
|
+
},
|
|
144
|
+
},
|
|
119
145
|
],
|
|
120
146
|
};
|
|
121
147
|
});
|
|
@@ -125,7 +151,7 @@ async function main() {
|
|
|
125
151
|
try {
|
|
126
152
|
if (request.params.name === 'initiate_call') {
|
|
127
153
|
const { message } = request.params.arguments as { message: string };
|
|
128
|
-
const result = await apiCall('/api/call', { message }) as {
|
|
154
|
+
const result = await apiCall('/api/call', { message }, TOOL_TIMEOUTS.initiate_call) as {
|
|
129
155
|
callId: string;
|
|
130
156
|
response: string;
|
|
131
157
|
contactMode?: 'voice' | 'whatsapp';
|
|
@@ -165,7 +191,7 @@ async function main() {
|
|
|
165
191
|
|
|
166
192
|
if (request.params.name === 'continue_call') {
|
|
167
193
|
const { call_id, message } = request.params.arguments as { call_id: string; message: string };
|
|
168
|
-
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 {
|
|
169
195
|
response: string;
|
|
170
196
|
contactMode?: 'voice' | 'whatsapp';
|
|
171
197
|
whatsappSessionWindow?: {
|
|
@@ -195,7 +221,7 @@ async function main() {
|
|
|
195
221
|
|
|
196
222
|
if (request.params.name === 'speak_to_user') {
|
|
197
223
|
const { call_id, message } = request.params.arguments as { call_id: string; message: string };
|
|
198
|
-
await apiCall(`/api/call/${call_id}/speak`, { message });
|
|
224
|
+
await apiCall(`/api/call/${call_id}/speak`, { message }, TOOL_TIMEOUTS.speak_to_user);
|
|
199
225
|
|
|
200
226
|
return {
|
|
201
227
|
content: [{ type: 'text', text: `Message spoken: "${message}"` }],
|
|
@@ -204,13 +230,25 @@ async function main() {
|
|
|
204
230
|
|
|
205
231
|
if (request.params.name === 'end_call') {
|
|
206
232
|
const { call_id, message } = request.params.arguments as { call_id: string; message: string };
|
|
207
|
-
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 };
|
|
208
234
|
|
|
209
235
|
return {
|
|
210
236
|
content: [{ type: 'text', text: `Call ended. Duration: ${result.durationSeconds}s` }],
|
|
211
237
|
};
|
|
212
238
|
}
|
|
213
239
|
|
|
240
|
+
if (request.params.name === 'send_message') {
|
|
241
|
+
const { message } = request.params.arguments as { message: string };
|
|
242
|
+
const result = await apiCall('/api/message', { message }, TOOL_TIMEOUTS.send_message) as { messageId: string; response: string };
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
content: [{
|
|
246
|
+
type: 'text',
|
|
247
|
+
text: `WhatsApp message sent.\n\nMessage ID: ${result.messageId}\n\nUser's response:\n${result.response}`,
|
|
248
|
+
}],
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
214
252
|
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
215
253
|
} catch (error) {
|
|
216
254
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|