@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.
@@ -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.0",
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.0",
4
- "description": "MCP server for the Peach WhatsApp messaging platform",
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
+ }