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.
Files changed (3) hide show
  1. package/README.md +28 -11
  2. package/index.ts +45 -7
  3. 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
- Used automatically via npx in Claude Code configurations. Add to your MCP settings:
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 or Telnyx account for phone calls
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) (Twilio/Telnyx) (to you)
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/Telnyx
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.1' },
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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "call-me-cloud-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "MCP client for Call-Me Cloud - lets Claude call you via phone",
5
5
  "type": "module",
6
6
  "bin": {