@peachai/mcp 0.1.0 → 0.1.2
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/.mcpregistry_github_token +1 -0
- package/.mcpregistry_registry_token +1 -0
- package/LICENSE +21 -0
- package/README.md +132 -0
- package/dist/index.js +1 -1
- package/package.json +20 -3
- package/server.json +25 -0
- package/src/client.ts +60 -0
- package/src/index.ts +22 -0
- package/src/tools/contacts.ts +59 -0
- package/src/tools/events.ts +142 -0
- package/src/tools/media.ts +55 -0
- package/src/tools/messages.ts +41 -0
- package/src/tools/templates.ts +130 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ghu_PQNCitmxq0E8YfTwFBcZf6l9hY3LXk2DOQO8
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"token":"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtY3AtcmVnaXN0cnkiLCJleHAiOjE3NzIyOTU5NTQsIm5iZiI6MTc3MjI5NTY1NCwiaWF0IjoxNzcyMjk1NjU0LCJhdXRoX21ldGhvZCI6ImdpdGh1Yi1hdCIsImF1dGhfbWV0aG9kX3N1YiI6InNoaG4xMjMiLCJwZXJtaXNzaW9ucyI6W3siYWN0aW9uIjoicHVibGlzaCIsInJlc291cmNlIjoiaW8uZ2l0aHViLnNoaG4xMjMvKiJ9LHsiYWN0aW9uIjoicHVibGlzaCIsInJlc291cmNlIjoiaW8uZ2l0aHViLnRyeXBlYWNoLWlvLyoifV19.e7iyCgI9PVtG0NiKp9K2T8U9yGpKI0nMv0eELfMI4FOo1qFvUWrP25bilBKWmm1EJD8OE6So5CnWKhUIOHToBA","expires_at":1772295954}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CStack Technologies Private Limited
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# @peachai/mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for the [Peach](https://trypeach.ai) WhatsApp messaging platform. Lets AI assistants like Claude send messages, manage templates, contacts, and media directly through your Peach account.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js 18 or higher
|
|
8
|
+
- A Peach account with an API key
|
|
9
|
+
|
|
10
|
+
## Getting your API key
|
|
11
|
+
|
|
12
|
+
Log in to [app.trypeach.ai](https://app.trypeach.ai), go to **Settings → API**, and copy your API key.
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
### Claude Desktop
|
|
17
|
+
|
|
18
|
+
Add the following to your `claude_desktop_config.json`:
|
|
19
|
+
|
|
20
|
+
**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
21
|
+
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"mcpServers": {
|
|
26
|
+
"peach": {
|
|
27
|
+
"command": "npx",
|
|
28
|
+
"args": ["-y", "@peachai/mcp"],
|
|
29
|
+
"env": {
|
|
30
|
+
"PEACH_API_KEY": "your-api-key"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Restart Claude Desktop. You should see the Peach tools available in your conversation.
|
|
38
|
+
|
|
39
|
+
### Cursor
|
|
40
|
+
|
|
41
|
+
Go to **Settings → MCP** and add:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"peach": {
|
|
46
|
+
"command": "npx",
|
|
47
|
+
"args": ["-y", "@peachai/mcp"],
|
|
48
|
+
"env": {
|
|
49
|
+
"PEACH_API_KEY": "your-api-key"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Claude Code
|
|
56
|
+
|
|
57
|
+
Run this command to add Peach AI to your Claude Code setup:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
claude mcp add --scope user --env PEACH_API_KEY=your-api-key peach -- npx -y @peachai/mcp
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or add it manually to your `.mcp.json`:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"peach": {
|
|
69
|
+
"type": "stdio",
|
|
70
|
+
"command": "npx",
|
|
71
|
+
"args": ["-y", "@peachai/mcp"],
|
|
72
|
+
"env": {
|
|
73
|
+
"PEACH_API_KEY": "your-api-key"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Available tools
|
|
81
|
+
|
|
82
|
+
### Messaging
|
|
83
|
+
| Tool | Description |
|
|
84
|
+
|------|-------------|
|
|
85
|
+
| `peach_send_template_message` | Send a WhatsApp template message to a contact |
|
|
86
|
+
| `peach_send_app_message` | Trigger a flow or app-initiated message |
|
|
87
|
+
| `peach_connect_to_ai_agent` | Send a template and connect the contact to an AI agent |
|
|
88
|
+
| `peach_get_event_status` | Check the status of a sent event |
|
|
89
|
+
| `peach_list_template_messages` | List sent template messages with filters |
|
|
90
|
+
|
|
91
|
+
### Broadcasts
|
|
92
|
+
| Tool | Description |
|
|
93
|
+
|------|-------------|
|
|
94
|
+
| `peach_launch_broadcast` | Create and launch a broadcast campaign |
|
|
95
|
+
|
|
96
|
+
### Templates
|
|
97
|
+
| Tool | Description |
|
|
98
|
+
|------|-------------|
|
|
99
|
+
| `peach_list_templates` | List all WhatsApp templates |
|
|
100
|
+
| `peach_get_template` | Get a specific template by ID or name |
|
|
101
|
+
| `peach_create_template` | Create a new WhatsApp template |
|
|
102
|
+
| `peach_update_template` | Update an existing template |
|
|
103
|
+
| `peach_submit_template` | Submit a template for Meta review |
|
|
104
|
+
| `peach_pause_template` | Pause an approved template |
|
|
105
|
+
| `peach_archive_template` | Archive a template |
|
|
106
|
+
|
|
107
|
+
### Contacts
|
|
108
|
+
| Tool | Description |
|
|
109
|
+
|------|-------------|
|
|
110
|
+
| `peach_create_contact` | Create or upsert a single contact |
|
|
111
|
+
| `peach_create_contacts` | Bulk create or upsert contacts |
|
|
112
|
+
| `peach_update_contact` | Update a contact by phone number |
|
|
113
|
+
|
|
114
|
+
### Media
|
|
115
|
+
| Tool | Description |
|
|
116
|
+
|------|-------------|
|
|
117
|
+
| `peach_list_media` | List uploaded media files |
|
|
118
|
+
| `peach_upload_media` | Upload a media file (base64) |
|
|
119
|
+
| `peach_delete_media` | Delete a media file |
|
|
120
|
+
|
|
121
|
+
### Messages
|
|
122
|
+
| Tool | Description |
|
|
123
|
+
|------|-------------|
|
|
124
|
+
| `peach_list_messages` | List messages with optional filters |
|
|
125
|
+
|
|
126
|
+
## Example prompts
|
|
127
|
+
|
|
128
|
+
- *"Send the order_confirmation template to +14155552671 with order ID 12345"*
|
|
129
|
+
- *"Create a new MARKETING template called summer_sale with a body saying 'Get 20% off this weekend!'"*
|
|
130
|
+
- *"List all my approved WhatsApp templates"*
|
|
131
|
+
- *"Update the contact +14155552671's name to John Smith"*
|
|
132
|
+
- *"Launch a broadcast using template promo_v2 to audience list abc123"*
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { registerMessageTools } from "./tools/messages.js";
|
|
|
8
8
|
import { registerMediaTools } from "./tools/media.js";
|
|
9
9
|
const server = new McpServer({
|
|
10
10
|
name: "peach",
|
|
11
|
-
version: "0.1.
|
|
11
|
+
version: "0.1.1",
|
|
12
12
|
});
|
|
13
13
|
registerEventTools(server);
|
|
14
14
|
registerTemplateTools(server);
|
package/package.json
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peachai/mcp",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "MCP server for the Peach WhatsApp
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "MCP server for the Peach AI WhatsApp Business Messaging Platform",
|
|
5
|
+
"author": "Peach AI <hello@trypeach.ai>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/trypeach-ai/mcp.git"
|
|
10
|
+
},
|
|
11
|
+
"mcpName": "io.github.trypeach-io/mcp",
|
|
12
|
+
"homepage": "https://github.com/trypeach-ai/mcp#readme",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"model-context-protocol",
|
|
16
|
+
"whatsapp",
|
|
17
|
+
"peach",
|
|
18
|
+
"peach-ai",
|
|
19
|
+
"messaging",
|
|
20
|
+
"claude"
|
|
21
|
+
],
|
|
5
22
|
"type": "module",
|
|
6
23
|
"bin": {
|
|
7
24
|
"peach-mcp": "dist/index.js"
|
|
@@ -22,4 +39,4 @@
|
|
|
22
39
|
"tsx": "^4.19.0",
|
|
23
40
|
"typescript": "^5.6.0"
|
|
24
41
|
}
|
|
25
|
-
}
|
|
42
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.trypeach-io/mcp",
|
|
4
|
+
"description": "Send and manage WhatsApp messages, contacts, templates, and events via Peach AI.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/trypeach-io/mcp",
|
|
7
|
+
"source": "github"
|
|
8
|
+
},
|
|
9
|
+
"version": "0.1.1",
|
|
10
|
+
"packages": [
|
|
11
|
+
{
|
|
12
|
+
"registryType": "npm",
|
|
13
|
+
"identifier": "@peachai/mcp",
|
|
14
|
+
"version": "0.1.1",
|
|
15
|
+
"transport": {
|
|
16
|
+
"type": "streamable-http",
|
|
17
|
+
"url": "https://app.trypeach.ai/api/mcp",
|
|
18
|
+
"headers": [
|
|
19
|
+
{ "name": "Authorization", "value": "{token}" }
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const BASE_URL = "https://app.trypeach.io";
|
|
2
|
+
|
|
3
|
+
function getApiKey(): string {
|
|
4
|
+
const key = process.env.PEACH_API_KEY;
|
|
5
|
+
if (!key) {
|
|
6
|
+
throw new Error("PEACH_API_KEY environment variable is not set");
|
|
7
|
+
}
|
|
8
|
+
return key;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function request<T>(
|
|
12
|
+
method: string,
|
|
13
|
+
path: string,
|
|
14
|
+
body?: unknown
|
|
15
|
+
): Promise<T> {
|
|
16
|
+
const url = `${BASE_URL}${path}`;
|
|
17
|
+
const headers: Record<string, string> = {
|
|
18
|
+
Authorization: getApiKey(),
|
|
19
|
+
"Content-Type": "application/json",
|
|
20
|
+
Accept: "application/json",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const response = await fetch(url, {
|
|
24
|
+
method,
|
|
25
|
+
headers,
|
|
26
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
31
|
+
try {
|
|
32
|
+
const errorBody = await response.json();
|
|
33
|
+
errorMessage = JSON.stringify(errorBody);
|
|
34
|
+
} catch {
|
|
35
|
+
// ignore parse error, use status message
|
|
36
|
+
}
|
|
37
|
+
throw new Error(errorMessage);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (response.status === 204) {
|
|
41
|
+
return undefined as T;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return response.json() as Promise<T>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const client = {
|
|
48
|
+
get<T>(path: string): Promise<T> {
|
|
49
|
+
return request<T>("GET", path);
|
|
50
|
+
},
|
|
51
|
+
post<T>(path: string, body: unknown): Promise<T> {
|
|
52
|
+
return request<T>("POST", path, body);
|
|
53
|
+
},
|
|
54
|
+
patch<T>(path: string, body: unknown): Promise<T> {
|
|
55
|
+
return request<T>("PATCH", path, body);
|
|
56
|
+
},
|
|
57
|
+
delete<T>(path: string): Promise<T> {
|
|
58
|
+
return request<T>("DELETE", path);
|
|
59
|
+
},
|
|
60
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { registerEventTools } from "./tools/events.js";
|
|
5
|
+
import { registerTemplateTools } from "./tools/templates.js";
|
|
6
|
+
import { registerContactTools } from "./tools/contacts.js";
|
|
7
|
+
import { registerMessageTools } from "./tools/messages.js";
|
|
8
|
+
import { registerMediaTools } from "./tools/media.js";
|
|
9
|
+
|
|
10
|
+
const server = new McpServer({
|
|
11
|
+
name: "peach",
|
|
12
|
+
version: "0.1.1",
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
registerEventTools(server);
|
|
16
|
+
registerTemplateTools(server);
|
|
17
|
+
registerContactTools(server);
|
|
18
|
+
registerMessageTools(server);
|
|
19
|
+
registerMediaTools(server);
|
|
20
|
+
|
|
21
|
+
const transport = new StdioServerTransport();
|
|
22
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { client } from "../client.js";
|
|
4
|
+
|
|
5
|
+
const ContactFields = {
|
|
6
|
+
phone_number: z.string().describe("Phone number in E.164 format (e.g. +14155552671)"),
|
|
7
|
+
name: z.string().optional().describe("Contact's full name"),
|
|
8
|
+
email: z.string().email().optional().describe("Contact's email address"),
|
|
9
|
+
timezone: z.string().optional().describe("Contact's timezone (e.g. America/New_York)"),
|
|
10
|
+
metadata: z.record(z.unknown()).optional().describe("Custom metadata key-value pairs (merged with existing)"),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function registerContactTools(server: McpServer): void {
|
|
14
|
+
server.tool(
|
|
15
|
+
"peach_create_contact",
|
|
16
|
+
"Create or upsert a single contact in the Peach account",
|
|
17
|
+
ContactFields,
|
|
18
|
+
async (params) => {
|
|
19
|
+
const result = await client.post("/api/v1/contacts", params);
|
|
20
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
server.tool(
|
|
25
|
+
"peach_create_contacts",
|
|
26
|
+
"Create or upsert multiple contacts in bulk in the Peach account",
|
|
27
|
+
{
|
|
28
|
+
contacts: z.array(z.object(ContactFields)).describe("Array of contacts to create or upsert"),
|
|
29
|
+
},
|
|
30
|
+
async ({ contacts }) => {
|
|
31
|
+
const result = await client.post("/api/v1/contacts", contacts);
|
|
32
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
server.tool(
|
|
37
|
+
"peach_update_contact",
|
|
38
|
+
"Update an existing contact identified by their phone number",
|
|
39
|
+
{
|
|
40
|
+
phone_number: z.string().describe("Phone number of the contact to update in E.164 format (e.g. +14155552671)"),
|
|
41
|
+
name: z.string().optional().describe("Updated full name"),
|
|
42
|
+
email: z.string().email().optional().describe("Updated email address"),
|
|
43
|
+
age: z.number().int().optional().describe("Updated age"),
|
|
44
|
+
gender: z.string().optional().describe("Updated gender"),
|
|
45
|
+
language: z.string().optional().describe("Updated language code (e.g. en)"),
|
|
46
|
+
nickname: z.string().optional().describe("Updated nickname"),
|
|
47
|
+
opted_out: z.boolean().optional().describe("Whether the contact has opted out of messaging"),
|
|
48
|
+
timezone: z.string().optional().describe("Updated timezone (e.g. America/New_York)"),
|
|
49
|
+
metadata: z.record(z.unknown()).optional().describe("Custom metadata to deep-merge with existing"),
|
|
50
|
+
},
|
|
51
|
+
async ({ phone_number, ...updates }) => {
|
|
52
|
+
const result = await client.patch(
|
|
53
|
+
`/api/v1/contacts/${encodeURIComponent(phone_number)}`,
|
|
54
|
+
updates
|
|
55
|
+
);
|
|
56
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { client } from "../client.js";
|
|
4
|
+
|
|
5
|
+
const ContactSchema = z.object({
|
|
6
|
+
phone_number: z.string().describe("Phone number in E.164 format (e.g. +14155552671)"),
|
|
7
|
+
name: z.string().optional().describe("Contact's full name"),
|
|
8
|
+
email: z.string().email().optional().describe("Contact's email address"),
|
|
9
|
+
metadata: z.record(z.unknown()).optional().describe("Custom metadata key-value pairs"),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export function registerEventTools(server: McpServer): void {
|
|
13
|
+
server.tool(
|
|
14
|
+
"peach_send_template_message",
|
|
15
|
+
"Send a WhatsApp template message to a contact via the Peach API",
|
|
16
|
+
{
|
|
17
|
+
contact: ContactSchema.describe("The recipient contact. phone_number is required."),
|
|
18
|
+
template_message: z.object({
|
|
19
|
+
whats_app_template_id: z.string().describe("ID of the WhatsApp template to use"),
|
|
20
|
+
liquid_values: z.record(z.unknown()).optional().describe("Variable values for template placeholders"),
|
|
21
|
+
business_phone_number: z.string().optional().describe("WA ID of the sending phone number to use"),
|
|
22
|
+
}).describe("Template message parameters"),
|
|
23
|
+
},
|
|
24
|
+
async ({ contact, template_message }) => {
|
|
25
|
+
const result = await client.post("/api/v1/events?sync=true", {
|
|
26
|
+
event_type: "send_template_message",
|
|
27
|
+
contact,
|
|
28
|
+
template_message,
|
|
29
|
+
});
|
|
30
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
server.tool(
|
|
35
|
+
"peach_launch_broadcast",
|
|
36
|
+
"Create and launch a broadcast campaign to multiple contacts via the Peach API",
|
|
37
|
+
{
|
|
38
|
+
broadcast: z.object({
|
|
39
|
+
whats_app_template_id: z.string().describe("ID of the WhatsApp template to use"),
|
|
40
|
+
name: z.string().describe("Name of the broadcast campaign"),
|
|
41
|
+
contacts: z.array(z.object({
|
|
42
|
+
phone_number: z.string().describe("Phone number in E.164 format"),
|
|
43
|
+
name: z.string().optional().describe("Contact name"),
|
|
44
|
+
liquid_values: z.record(z.unknown()).optional().describe("Per-contact template variable values"),
|
|
45
|
+
})).optional().describe("Explicit list of contacts to send to"),
|
|
46
|
+
audience_id: z.string().optional().describe("ID of an existing audience/list to send to"),
|
|
47
|
+
phone_number: z.string().optional().describe("WA ID of the sending phone number to use"),
|
|
48
|
+
delivery_mode: z.enum(["instant", "smart"]).optional().describe("Delivery mode: instant (asap) or smart (gradual)"),
|
|
49
|
+
scheduled_launch_time: z.string().optional().describe("ISO 8601 datetime to schedule the send (e.g. 2024-01-15T10:00:00Z)"),
|
|
50
|
+
liquid_values: z.record(z.unknown()).optional().describe("Global template variable values for all contacts"),
|
|
51
|
+
}).describe("Broadcast parameters"),
|
|
52
|
+
},
|
|
53
|
+
async ({ broadcast }) => {
|
|
54
|
+
const result = await client.post("/api/v1/events?sync=true", {
|
|
55
|
+
event_type: "send_broadcast",
|
|
56
|
+
broadcast,
|
|
57
|
+
});
|
|
58
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
server.tool(
|
|
63
|
+
"peach_connect_to_ai_agent",
|
|
64
|
+
"Send a template message to a contact and connect them to an AI agent for automated follow-up",
|
|
65
|
+
{
|
|
66
|
+
contact: ContactSchema.describe("The recipient contact. phone_number is required."),
|
|
67
|
+
template_message: z.object({
|
|
68
|
+
whats_app_template_id: z.string().describe("ID of the WhatsApp template to send"),
|
|
69
|
+
liquid_values: z.record(z.unknown()).optional().describe("Variable values for template placeholders"),
|
|
70
|
+
business_phone_number: z.string().optional().describe("WA ID of the sending phone number to use"),
|
|
71
|
+
}).describe("Template message parameters"),
|
|
72
|
+
ai_agent: z.object({
|
|
73
|
+
id: z.string().describe("ID of the AI agent to connect the contact to"),
|
|
74
|
+
}).optional().describe("AI agent to connect the contact to after the template message"),
|
|
75
|
+
},
|
|
76
|
+
async ({ contact, template_message, ai_agent }) => {
|
|
77
|
+
const result = await client.post("/api/v1/events?sync=true", {
|
|
78
|
+
event_type: "connect_to_ai_agent",
|
|
79
|
+
contact,
|
|
80
|
+
template_message,
|
|
81
|
+
ai_agent,
|
|
82
|
+
});
|
|
83
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
server.tool(
|
|
88
|
+
"peach_send_app_message",
|
|
89
|
+
"Trigger a flow or app-initiated message to a contact via the Peach API",
|
|
90
|
+
{
|
|
91
|
+
contact: ContactSchema.describe("The recipient contact. phone_number is required."),
|
|
92
|
+
flow: z.object({
|
|
93
|
+
id: z.string().describe("ID of the flow to execute"),
|
|
94
|
+
liquid_values: z.record(z.unknown()).optional().describe("Variable values for the flow"),
|
|
95
|
+
whats_app_template_id: z.string().optional().describe("Fallback template ID if the reply window is closed"),
|
|
96
|
+
}).describe("Flow parameters"),
|
|
97
|
+
},
|
|
98
|
+
async ({ contact, flow }) => {
|
|
99
|
+
const result = await client.post("/api/v1/events?sync=true", {
|
|
100
|
+
event_type: "send_app_message",
|
|
101
|
+
contact,
|
|
102
|
+
flow,
|
|
103
|
+
});
|
|
104
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
server.tool(
|
|
109
|
+
"peach_get_event_status",
|
|
110
|
+
"Get the status and details of a specific event by its ID",
|
|
111
|
+
{
|
|
112
|
+
event_id: z.string().describe("The ID of the event to retrieve"),
|
|
113
|
+
},
|
|
114
|
+
async ({ event_id }) => {
|
|
115
|
+
const result = await client.get(`/api/v1/events/${event_id}`);
|
|
116
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
server.tool(
|
|
121
|
+
"peach_list_template_messages",
|
|
122
|
+
"List template messages sent via the Peach API with optional filters",
|
|
123
|
+
{
|
|
124
|
+
page: z.number().int().positive().optional().describe("Page number for pagination"),
|
|
125
|
+
per_page: z.number().int().positive().optional().describe("Number of results per page"),
|
|
126
|
+
status: z.string().optional().describe("Filter by message status (e.g. delivered, read, failed)"),
|
|
127
|
+
phone_number: z.string().optional().describe("Filter by recipient phone number in E.164 format"),
|
|
128
|
+
template_name: z.string().optional().describe("Filter by template name"),
|
|
129
|
+
from: z.string().optional().describe("ISO 8601 start datetime for filtering (e.g. 2024-01-01T00:00:00Z)"),
|
|
130
|
+
to: z.string().optional().describe("ISO 8601 end datetime for filtering (e.g. 2024-01-31T23:59:59Z)"),
|
|
131
|
+
},
|
|
132
|
+
async (params) => {
|
|
133
|
+
const query = new URLSearchParams();
|
|
134
|
+
for (const [key, value] of Object.entries(params)) {
|
|
135
|
+
if (value !== undefined) query.set(key, String(value));
|
|
136
|
+
}
|
|
137
|
+
const qs = query.toString();
|
|
138
|
+
const result = await client.get(`/api/v1/template_messages${qs ? `?${qs}` : ""}`);
|
|
139
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { client } from "../client.js";
|
|
4
|
+
|
|
5
|
+
export function registerMediaTools(server: McpServer): void {
|
|
6
|
+
server.tool(
|
|
7
|
+
"peach_list_media",
|
|
8
|
+
"List media files uploaded to the Peach account",
|
|
9
|
+
{
|
|
10
|
+
page: z.number().int().positive().optional().describe("Page number for pagination"),
|
|
11
|
+
},
|
|
12
|
+
async (params) => {
|
|
13
|
+
const query = new URLSearchParams();
|
|
14
|
+
if (params.page !== undefined) query.set("page", String(params.page));
|
|
15
|
+
const qs = query.toString();
|
|
16
|
+
const result = await client.get(`/api/v1/medias${qs ? `?${qs}` : ""}`);
|
|
17
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
server.tool(
|
|
22
|
+
"peach_upload_media",
|
|
23
|
+
"Upload a media file to Peach using base64-encoded file content",
|
|
24
|
+
{
|
|
25
|
+
data: z.string().describe("Base64-encoded file content"),
|
|
26
|
+
filename: z.string().describe("Filename including extension (e.g. photo.jpg)"),
|
|
27
|
+
content_type: z.string().optional().describe("MIME type of the file (e.g. image/jpeg, video/mp4)"),
|
|
28
|
+
},
|
|
29
|
+
async ({ data, filename, content_type }) => {
|
|
30
|
+
const result = await client.post("/api/v1/medias", {
|
|
31
|
+
media: { data, filename, content_type },
|
|
32
|
+
});
|
|
33
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
server.tool(
|
|
38
|
+
"peach_delete_media",
|
|
39
|
+
"Delete a media file from the Peach account by its ID",
|
|
40
|
+
{
|
|
41
|
+
media_id: z.string().describe("The ID of the media file to delete"),
|
|
42
|
+
},
|
|
43
|
+
async ({ media_id }) => {
|
|
44
|
+
const result = await client.delete(`/api/v1/medias/${media_id}`);
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: "text",
|
|
49
|
+
text: result !== undefined ? JSON.stringify(result, null, 2) : "Media deleted successfully",
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { client } from "../client.js";
|
|
4
|
+
|
|
5
|
+
export function registerMessageTools(server: McpServer): void {
|
|
6
|
+
server.tool(
|
|
7
|
+
"peach_list_messages",
|
|
8
|
+
"List messages in the Peach account with optional filters",
|
|
9
|
+
{
|
|
10
|
+
page: z.number().int().positive().optional().describe("Page number for pagination"),
|
|
11
|
+
per_page: z.number().int().positive().optional().describe("Number of results per page"),
|
|
12
|
+
phone_number: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("Filter by contact phone number in E.164 format"),
|
|
16
|
+
direction: z
|
|
17
|
+
.enum(["inbound", "outbound"])
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Filter by message direction"),
|
|
20
|
+
from: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("ISO 8601 start datetime for filtering (e.g. 2024-01-01T00:00:00Z)"),
|
|
24
|
+
to: z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("ISO 8601 end datetime for filtering (e.g. 2024-01-31T23:59:59Z)"),
|
|
28
|
+
},
|
|
29
|
+
async (params) => {
|
|
30
|
+
const query = new URLSearchParams();
|
|
31
|
+
for (const [key, value] of Object.entries(params)) {
|
|
32
|
+
if (value !== undefined) {
|
|
33
|
+
query.set(key, String(value));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const qs = query.toString();
|
|
37
|
+
const result = await client.get(`/api/v1/messages${qs ? `?${qs}` : ""}`);
|
|
38
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { client } from "../client.js";
|
|
4
|
+
|
|
5
|
+
const ComponentAttributeSchema = z.object({
|
|
6
|
+
component_type: z.enum(["header", "body", "footer", "buttons"]).describe("Component type"),
|
|
7
|
+
sub_type: z.string().optional().describe("Button sub-type (e.g. quick_reply, url, phone_number)"),
|
|
8
|
+
data_type: z.string().optional().describe("Data type for the component (e.g. text, image, video, document)"),
|
|
9
|
+
name: z.string().optional().describe("Button label or component name"),
|
|
10
|
+
value: z.string().optional().describe("Text content or button value/URL"),
|
|
11
|
+
index: z.number().int().optional().describe("Position index of the component"),
|
|
12
|
+
id: z.number().int().optional().describe("Component ID (required when updating existing components)"),
|
|
13
|
+
_destroy: z.boolean().optional().describe("Set to true to remove this component during update"),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export function registerTemplateTools(server: McpServer): void {
|
|
17
|
+
server.tool(
|
|
18
|
+
"peach_list_templates",
|
|
19
|
+
"List all WhatsApp templates in the Peach account",
|
|
20
|
+
{
|
|
21
|
+
page: z.number().int().positive().optional().describe("Page number for pagination"),
|
|
22
|
+
},
|
|
23
|
+
async (params) => {
|
|
24
|
+
const query = new URLSearchParams();
|
|
25
|
+
if (params.page !== undefined) query.set("page", String(params.page));
|
|
26
|
+
const qs = query.toString();
|
|
27
|
+
const result = await client.get(`/api/v1/whats_app_templates${qs ? `?${qs}` : ""}`);
|
|
28
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
server.tool(
|
|
33
|
+
"peach_get_template",
|
|
34
|
+
"Get a specific WhatsApp template by its ID or template name",
|
|
35
|
+
{
|
|
36
|
+
id: z.string().describe("The template ID or template_name"),
|
|
37
|
+
},
|
|
38
|
+
async ({ id }) => {
|
|
39
|
+
const result = await client.get(`/api/v1/whats_app_templates/${id}`);
|
|
40
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
server.tool(
|
|
45
|
+
"peach_create_template",
|
|
46
|
+
"Create a new WhatsApp template in the Peach account. Must include at least a body component in components_attributes.",
|
|
47
|
+
{
|
|
48
|
+
template_name: z.string().describe("Unique name for the template (lowercase, underscores only, e.g. order_confirmation)"),
|
|
49
|
+
language: z.string().describe("Language code for the template (e.g. en, en_US)"),
|
|
50
|
+
category: z.enum(["MARKETING", "UTILITY", "AUTHENTICATION"]).describe("Template category as required by WhatsApp"),
|
|
51
|
+
components_attributes: z.array(ComponentAttributeSchema).describe(
|
|
52
|
+
"Template components. Must include a body component. Each component has component_type, value, and optional sub_type/data_type/name/index."
|
|
53
|
+
),
|
|
54
|
+
purpose: z.string().optional().describe("Purpose of the template (e.g. general)"),
|
|
55
|
+
whats_app_business_account_id: z.string().optional().describe("External ID of the WhatsApp Business Account to use (defaults to the account's only WABA if there is one)"),
|
|
56
|
+
folder: z.string().optional().describe("Folder to organise the template into"),
|
|
57
|
+
},
|
|
58
|
+
async ({ template_name, language, category, components_attributes, purpose, whats_app_business_account_id, folder }) => {
|
|
59
|
+
const result = await client.post("/api/v1/whats_app_templates", {
|
|
60
|
+
whats_app_template: {
|
|
61
|
+
template_name,
|
|
62
|
+
language,
|
|
63
|
+
category,
|
|
64
|
+
components_attributes,
|
|
65
|
+
purpose,
|
|
66
|
+
whats_app_business_account_id,
|
|
67
|
+
folder,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
server.tool(
|
|
75
|
+
"peach_update_template",
|
|
76
|
+
"Update an existing WhatsApp template's components or category",
|
|
77
|
+
{
|
|
78
|
+
id: z.string().describe("The template ID or template_name"),
|
|
79
|
+
components_attributes: z.array(ComponentAttributeSchema).optional().describe(
|
|
80
|
+
"Updated components. Include id on existing components to update them; set _destroy: true to remove one."
|
|
81
|
+
),
|
|
82
|
+
category: z.enum(["MARKETING", "UTILITY", "AUTHENTICATION"]).optional().describe("Updated template category"),
|
|
83
|
+
},
|
|
84
|
+
async ({ id, components_attributes, category }) => {
|
|
85
|
+
const result = await client.patch(`/api/v1/whats_app_templates/${id}`, {
|
|
86
|
+
whats_app_template: {
|
|
87
|
+
components_attributes,
|
|
88
|
+
category,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
server.tool(
|
|
96
|
+
"peach_archive_template",
|
|
97
|
+
"Archive a WhatsApp template (must be approved, rejected, or draft status)",
|
|
98
|
+
{
|
|
99
|
+
id: z.string().describe("The template ID or template_name"),
|
|
100
|
+
},
|
|
101
|
+
async ({ id }) => {
|
|
102
|
+
const result = await client.patch(`/api/v1/whats_app_templates/${id}/archive`, {});
|
|
103
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
server.tool(
|
|
108
|
+
"peach_pause_template",
|
|
109
|
+
"Pause an approved WhatsApp template to temporarily stop it from being used",
|
|
110
|
+
{
|
|
111
|
+
id: z.string().describe("The template ID or template_name"),
|
|
112
|
+
},
|
|
113
|
+
async ({ id }) => {
|
|
114
|
+
const result = await client.patch(`/api/v1/whats_app_templates/${id}/pause`, {});
|
|
115
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
server.tool(
|
|
120
|
+
"peach_submit_template",
|
|
121
|
+
"Submit a WhatsApp template for review and approval by Meta",
|
|
122
|
+
{
|
|
123
|
+
id: z.string().describe("The template ID or template_name"),
|
|
124
|
+
},
|
|
125
|
+
async ({ id }) => {
|
|
126
|
+
const result = await client.patch(`/api/v1/whats_app_templates/${id}/submit`, {});
|
|
127
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"declarationMap": true,
|
|
13
|
+
"sourceMap": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|