@zyphr-dev/mcp-server 0.1.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 +117 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2315 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
- package/src/client.ts +25 -0
- package/src/config.test.ts +64 -0
- package/src/config.ts +33 -0
- package/src/index.ts +24 -0
- package/src/integration/quickstart/email.ts +646 -0
- package/src/integration/quickstart/inbox.ts +222 -0
- package/src/integration/quickstart/index.ts +45 -0
- package/src/integration/quickstart/push.ts +216 -0
- package/src/integration/quickstart/quickstart.test.ts +108 -0
- package/src/integration/quickstart/sms.ts +205 -0
- package/src/integration/quickstart/webhook.ts +664 -0
- package/src/integration/quickstart-types.ts +31 -0
- package/src/integration/sdk-snippets.test.ts +63 -0
- package/src/integration/sdk-snippets.ts +248 -0
- package/src/result.test.ts +107 -0
- package/src/result.ts +65 -0
- package/src/schemas.ts +231 -0
- package/src/server.ts +26 -0
- package/src/tools/index.ts +7 -0
- package/src/tools/integration.ts +54 -0
- package/src/tools/send.ts +153 -0
- package/src/tools/subscribers.ts +126 -0
- package/src/tools/templates.ts +87 -0
- package/src/tools/webhooks.ts +82 -0
package/src/schemas.ts
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
const emailAddress = z.object({
|
|
4
|
+
email: z.string().email(),
|
|
5
|
+
name: z.string().optional(),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const recipient = z.union([z.string().email(), emailAddress]);
|
|
9
|
+
|
|
10
|
+
export const sendEmailShape = {
|
|
11
|
+
to: z
|
|
12
|
+
.union([recipient, z.array(recipient).min(1)])
|
|
13
|
+
.describe('Recipient email address (string or {email,name}) or an array of them'),
|
|
14
|
+
from: z
|
|
15
|
+
.union([z.string().email(), emailAddress])
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('Sender address. Defaults to the account-level "from" address.'),
|
|
18
|
+
replyTo: z.union([z.string().email(), emailAddress]).optional(),
|
|
19
|
+
cc: z.array(z.string().email()).optional(),
|
|
20
|
+
bcc: z.array(z.string().email()).optional(),
|
|
21
|
+
subject: z.string().min(1),
|
|
22
|
+
html: z.string().optional().describe('Rendered HTML body. Mutually exclusive with templateId.'),
|
|
23
|
+
text: z.string().optional().describe('Plain-text body. Mutually exclusive with templateId.'),
|
|
24
|
+
templateId: z.string().optional().describe('Template ID. When set, html/text are ignored.'),
|
|
25
|
+
templateData: z
|
|
26
|
+
.record(z.unknown())
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Variables to interpolate into the template'),
|
|
29
|
+
tags: z.array(z.string()).optional(),
|
|
30
|
+
metadata: z.record(z.unknown()).optional(),
|
|
31
|
+
subscriberId: z.string().optional(),
|
|
32
|
+
category: z.string().optional(),
|
|
33
|
+
scheduledAt: z
|
|
34
|
+
.string()
|
|
35
|
+
.datetime()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe('ISO 8601 timestamp for scheduled delivery'),
|
|
38
|
+
} as const;
|
|
39
|
+
|
|
40
|
+
export const sendPushShape = {
|
|
41
|
+
userId: z.string().optional().describe('Send to all devices for this user/subscriber'),
|
|
42
|
+
deviceId: z.string().optional().describe('Send to a specific device only'),
|
|
43
|
+
title: z.string().optional(),
|
|
44
|
+
body: z.string().optional(),
|
|
45
|
+
data: z.record(z.unknown()).optional().describe('Custom data payload delivered to the device'),
|
|
46
|
+
badge: z.number().int().nonnegative().optional(),
|
|
47
|
+
sound: z.string().optional(),
|
|
48
|
+
imageUrl: z.string().url().optional(),
|
|
49
|
+
contentAvailable: z.boolean().optional().describe('Silent/background push'),
|
|
50
|
+
tags: z.array(z.string()).optional(),
|
|
51
|
+
metadata: z.record(z.unknown()).optional(),
|
|
52
|
+
collapseKey: z.string().optional(),
|
|
53
|
+
subscriberId: z.string().optional(),
|
|
54
|
+
subscriberExternalId: z.string().optional(),
|
|
55
|
+
category: z.string().optional(),
|
|
56
|
+
force: z.boolean().optional().describe('Skip subscriber preference checks'),
|
|
57
|
+
sendAt: z.string().datetime().optional(),
|
|
58
|
+
delay: z.number().int().nonnegative().optional(),
|
|
59
|
+
} as const;
|
|
60
|
+
|
|
61
|
+
export const sendSmsShape = {
|
|
62
|
+
to: z.string().min(1).describe('Recipient phone number in E.164 format (e.g. +14155551234)'),
|
|
63
|
+
from: z.string().optional().describe('Sender phone number or sender ID'),
|
|
64
|
+
body: z.string().min(1),
|
|
65
|
+
subscriberId: z.string().optional(),
|
|
66
|
+
scheduledAt: z.string().datetime().optional(),
|
|
67
|
+
metadata: z.record(z.unknown()).optional(),
|
|
68
|
+
} as const;
|
|
69
|
+
|
|
70
|
+
// Integration
|
|
71
|
+
|
|
72
|
+
export const sdkLanguages = ['node', 'python', 'ruby', 'go', 'php', 'csharp'] as const;
|
|
73
|
+
export type SdkLanguage = (typeof sdkLanguages)[number];
|
|
74
|
+
|
|
75
|
+
export const quickstartChannels = ['email', 'push', 'sms', 'inbox', 'webhook'] as const;
|
|
76
|
+
export type QuickstartChannel = (typeof quickstartChannels)[number];
|
|
77
|
+
|
|
78
|
+
export const getQuickstartShape = {
|
|
79
|
+
channel: z
|
|
80
|
+
.enum(quickstartChannels)
|
|
81
|
+
.describe('Which Zyphr channel to wire up'),
|
|
82
|
+
language: z
|
|
83
|
+
.enum(['node', 'python', 'ruby', 'go', 'php', 'csharp'])
|
|
84
|
+
.describe('Target language for the integration'),
|
|
85
|
+
framework: z
|
|
86
|
+
.string()
|
|
87
|
+
.optional()
|
|
88
|
+
.describe(
|
|
89
|
+
'Optional framework hint (e.g. "express", "nextjs", "flask", "fastapi", "rails", "gin", "laravel", "aspnetcore"). Falls back to plain SDK code when unrecognized.',
|
|
90
|
+
),
|
|
91
|
+
} as const;
|
|
92
|
+
|
|
93
|
+
export const getSdkInstallShape = {
|
|
94
|
+
language: z
|
|
95
|
+
.enum(sdkLanguages)
|
|
96
|
+
.describe('Target language for the integration'),
|
|
97
|
+
packageManager: z
|
|
98
|
+
.string()
|
|
99
|
+
.optional()
|
|
100
|
+
.describe(
|
|
101
|
+
'Optional package manager override (e.g. "yarn" instead of "npm"). When recognized, only that manager is returned; otherwise the full list is returned.',
|
|
102
|
+
),
|
|
103
|
+
} as const;
|
|
104
|
+
|
|
105
|
+
// Templates
|
|
106
|
+
|
|
107
|
+
export const listTemplatesShape = {
|
|
108
|
+
limit: z.number().int().positive().max(200).optional(),
|
|
109
|
+
offset: z.number().int().nonnegative().optional(),
|
|
110
|
+
} as const;
|
|
111
|
+
|
|
112
|
+
export const getTemplateShape = {
|
|
113
|
+
id: z.string().min(1).describe('Template ID'),
|
|
114
|
+
} as const;
|
|
115
|
+
|
|
116
|
+
export const renderTemplateShape = {
|
|
117
|
+
id: z.string().min(1).describe('Template ID'),
|
|
118
|
+
variables: z
|
|
119
|
+
.record(z.unknown())
|
|
120
|
+
.describe('Key/value variables to interpolate into the template'),
|
|
121
|
+
} as const;
|
|
122
|
+
|
|
123
|
+
export const createTemplateShape = {
|
|
124
|
+
name: z.string().min(1),
|
|
125
|
+
description: z.string().optional(),
|
|
126
|
+
subject: z.string().optional().describe('Default subject (email templates)'),
|
|
127
|
+
html: z.string().optional(),
|
|
128
|
+
text: z.string().optional(),
|
|
129
|
+
} as const;
|
|
130
|
+
|
|
131
|
+
// Subscribers
|
|
132
|
+
|
|
133
|
+
export const findSubscriberShape = {
|
|
134
|
+
externalId: z
|
|
135
|
+
.string()
|
|
136
|
+
.min(1)
|
|
137
|
+
.describe('Subscriber external ID (your application user/customer ID)'),
|
|
138
|
+
} as const;
|
|
139
|
+
|
|
140
|
+
export const listSubscribersShape = {
|
|
141
|
+
status: z.enum(['active', 'inactive']).optional(),
|
|
142
|
+
email: z.string().email().optional(),
|
|
143
|
+
limit: z.number().int().positive().max(200).optional(),
|
|
144
|
+
offset: z.number().int().nonnegative().optional(),
|
|
145
|
+
} as const;
|
|
146
|
+
|
|
147
|
+
export const createSubscriberShape = {
|
|
148
|
+
externalId: z.string().min(1).describe('Your application user/customer ID'),
|
|
149
|
+
email: z.string().email().optional(),
|
|
150
|
+
phone: z.string().optional(),
|
|
151
|
+
name: z.string().optional(),
|
|
152
|
+
avatarUrl: z.string().url().optional(),
|
|
153
|
+
timezone: z.string().optional(),
|
|
154
|
+
locale: z.string().optional(),
|
|
155
|
+
metadata: z.record(z.unknown()).optional(),
|
|
156
|
+
} as const;
|
|
157
|
+
|
|
158
|
+
export const updateSubscriberShape = {
|
|
159
|
+
id: z.string().min(1).describe('Zyphr subscriber ID'),
|
|
160
|
+
email: z.string().email().nullable().optional(),
|
|
161
|
+
phone: z.string().nullable().optional(),
|
|
162
|
+
name: z.string().nullable().optional(),
|
|
163
|
+
avatarUrl: z.string().url().nullable().optional(),
|
|
164
|
+
timezone: z.string().optional(),
|
|
165
|
+
locale: z.string().optional(),
|
|
166
|
+
metadata: z.record(z.unknown()).optional(),
|
|
167
|
+
status: z.enum(['active', 'inactive']).optional(),
|
|
168
|
+
} as const;
|
|
169
|
+
|
|
170
|
+
export const setSubscriberPreferencesShape = {
|
|
171
|
+
id: z.string().min(1).describe('Zyphr subscriber ID'),
|
|
172
|
+
preferences: z
|
|
173
|
+
.array(
|
|
174
|
+
z.object({
|
|
175
|
+
categoryId: z.string().optional(),
|
|
176
|
+
channel: z.string().optional().describe('email | push | sms | in_app'),
|
|
177
|
+
enabled: z.boolean().optional(),
|
|
178
|
+
}),
|
|
179
|
+
)
|
|
180
|
+
.min(1),
|
|
181
|
+
} as const;
|
|
182
|
+
|
|
183
|
+
// Webhooks
|
|
184
|
+
|
|
185
|
+
export const listWebhooksShape = {
|
|
186
|
+
limit: z.number().int().positive().max(200).optional(),
|
|
187
|
+
offset: z.number().int().nonnegative().optional(),
|
|
188
|
+
} as const;
|
|
189
|
+
|
|
190
|
+
export const createWebhookShape = {
|
|
191
|
+
url: z.string().url().describe('Receiver URL that Zyphr will POST events to'),
|
|
192
|
+
events: z
|
|
193
|
+
.array(z.string().min(1))
|
|
194
|
+
.min(1)
|
|
195
|
+
.describe('Event types to subscribe to (e.g. ["email.*", "subscriber.created"])'),
|
|
196
|
+
description: z.string().optional(),
|
|
197
|
+
secret: z
|
|
198
|
+
.string()
|
|
199
|
+
.optional()
|
|
200
|
+
.describe('Optional secret used to sign payloads. If omitted, Zyphr generates one.'),
|
|
201
|
+
metadata: z.record(z.unknown()).optional(),
|
|
202
|
+
headers: z.record(z.string()).optional().describe('Custom headers to send with every delivery'),
|
|
203
|
+
version: z.string().optional(),
|
|
204
|
+
rateLimit: z.number().int().positive().optional(),
|
|
205
|
+
} as const;
|
|
206
|
+
|
|
207
|
+
export const getWebhookDeliveriesShape = {
|
|
208
|
+
webhookId: z.string().min(1).describe('Webhook endpoint ID'),
|
|
209
|
+
status: z.enum(['pending', 'delivering', 'delivered', 'failed', 'exhausted']).optional(),
|
|
210
|
+
eventType: z.string().optional(),
|
|
211
|
+
search: z.string().optional(),
|
|
212
|
+
startDate: z.string().datetime().optional(),
|
|
213
|
+
endDate: z.string().datetime().optional(),
|
|
214
|
+
limit: z.number().int().positive().max(200).optional(),
|
|
215
|
+
offset: z.number().int().nonnegative().optional(),
|
|
216
|
+
} as const;
|
|
217
|
+
|
|
218
|
+
export const sendInboxMessageShape = {
|
|
219
|
+
subscriberId: z.string().min(1).describe('Subscriber to deliver the in-app message to'),
|
|
220
|
+
title: z.string().min(1),
|
|
221
|
+
body: z.string().optional(),
|
|
222
|
+
actionUrl: z.string().url().optional(),
|
|
223
|
+
actionLabel: z.string().optional(),
|
|
224
|
+
imageUrl: z.string().url().optional(),
|
|
225
|
+
icon: z.string().optional(),
|
|
226
|
+
category: z.string().optional(),
|
|
227
|
+
priority: z.enum(['low', 'normal', 'high', 'urgent']).optional(),
|
|
228
|
+
data: z.record(z.unknown()).optional(),
|
|
229
|
+
tags: z.array(z.string()).optional(),
|
|
230
|
+
expiresAt: z.string().datetime().optional(),
|
|
231
|
+
} as const;
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { loadToolGuards } from './config.js';
|
|
3
|
+
import { registerSendTools } from './tools/send.js';
|
|
4
|
+
import { registerSubscriberTools } from './tools/subscribers.js';
|
|
5
|
+
import { registerTemplateTools } from './tools/templates.js';
|
|
6
|
+
import { registerIntegrationTools } from './tools/integration.js';
|
|
7
|
+
import { registerWebhookTools } from './tools/webhooks.js';
|
|
8
|
+
|
|
9
|
+
export const SERVER_NAME = 'zyphr';
|
|
10
|
+
export const SERVER_VERSION = '0.1.0';
|
|
11
|
+
|
|
12
|
+
export function createServer(): McpServer {
|
|
13
|
+
const server = new McpServer(
|
|
14
|
+
{ name: SERVER_NAME, version: SERVER_VERSION },
|
|
15
|
+
{ capabilities: { tools: {} } },
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const guards = loadToolGuards();
|
|
19
|
+
registerSendTools(server, guards);
|
|
20
|
+
registerTemplateTools(server, guards);
|
|
21
|
+
registerSubscriberTools(server, guards);
|
|
22
|
+
registerWebhookTools(server, guards);
|
|
23
|
+
registerIntegrationTools(server, guards);
|
|
24
|
+
|
|
25
|
+
return server;
|
|
26
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Tool registration is performed inside `createServer` (src/server.ts).
|
|
2
|
+
// Each tool file exports a `register*Tools(server, guards)` function.
|
|
3
|
+
export { registerSendTools } from './send.js';
|
|
4
|
+
export { registerTemplateTools } from './templates.js';
|
|
5
|
+
export { registerSubscriberTools } from './subscribers.js';
|
|
6
|
+
export { registerWebhookTools } from './webhooks.js';
|
|
7
|
+
export { registerIntegrationTools } from './integration.js';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { isToolEnabled, type ToolGuards } from '../config.js';
|
|
3
|
+
import { resolveQuickstart } from '../integration/quickstart/index.js';
|
|
4
|
+
import { resolveInstallEntry } from '../integration/sdk-snippets.js';
|
|
5
|
+
import { toolResult } from '../result.js';
|
|
6
|
+
import { getQuickstartShape, getSdkInstallShape } from '../schemas.js';
|
|
7
|
+
|
|
8
|
+
export function registerIntegrationTools(server: McpServer, guards: ToolGuards): void {
|
|
9
|
+
if (isToolEnabled({ name: 'get_sdk_install_for_language', mutates: false }, guards)) {
|
|
10
|
+
server.registerTool(
|
|
11
|
+
'get_sdk_install_for_language',
|
|
12
|
+
{
|
|
13
|
+
title: 'Get SDK install instructions for a language',
|
|
14
|
+
description:
|
|
15
|
+
'Returns install commands, init snippet, env vars, and docs URL for the chosen language. Use this when wiring Zyphr into a new project so the AI can drop the correct package + client init code.',
|
|
16
|
+
inputSchema: getSdkInstallShape,
|
|
17
|
+
},
|
|
18
|
+
async (args) => {
|
|
19
|
+
const entry = resolveInstallEntry(args.language, args.packageManager);
|
|
20
|
+
return toolResult(entry);
|
|
21
|
+
},
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (isToolEnabled({ name: 'get_quickstart_for_channel', mutates: false }, guards)) {
|
|
26
|
+
server.registerTool(
|
|
27
|
+
'get_quickstart_for_channel',
|
|
28
|
+
{
|
|
29
|
+
title: 'Get quickstart code for a channel + language',
|
|
30
|
+
description:
|
|
31
|
+
'Returns drop-in service file(s) for the chosen Zyphr channel (email/push/sms/inbox/webhook), language, and optional framework (express, nextjs, flask, fastapi, rails, laravel, aspnetcore). Webhook handlers ALWAYS verify HMAC signatures. Unknown frameworks fall back to plain SDK code.',
|
|
32
|
+
inputSchema: getQuickstartShape,
|
|
33
|
+
},
|
|
34
|
+
async (args) => {
|
|
35
|
+
const resolved = resolveQuickstart({
|
|
36
|
+
channel: args.channel,
|
|
37
|
+
language: args.language,
|
|
38
|
+
framework: args.framework,
|
|
39
|
+
});
|
|
40
|
+
if (!resolved) {
|
|
41
|
+
return toolResult({
|
|
42
|
+
error: `No quickstart available for channel=${args.channel} language=${args.language}`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const { result, frameworkRecognized } = resolved;
|
|
46
|
+
return toolResult({
|
|
47
|
+
...result,
|
|
48
|
+
frameworkRecognized,
|
|
49
|
+
requestedFramework: args.framework ?? null,
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { getZyphrClient } from '../client.js';
|
|
3
|
+
import { isToolEnabled, type ToolGuards } from '../config.js';
|
|
4
|
+
import { runTool } from '../result.js';
|
|
5
|
+
import {
|
|
6
|
+
sendEmailShape,
|
|
7
|
+
sendInboxMessageShape,
|
|
8
|
+
sendPushShape,
|
|
9
|
+
sendSmsShape,
|
|
10
|
+
} from '../schemas.js';
|
|
11
|
+
|
|
12
|
+
function normalizeEmailAddress(value: unknown): { email: string; name?: string } | undefined {
|
|
13
|
+
if (typeof value === 'string') return { email: value };
|
|
14
|
+
if (value && typeof value === 'object' && 'email' in value) {
|
|
15
|
+
return value as { email: string; name?: string };
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeRecipients(to: unknown): { email: string; name?: string }[] {
|
|
21
|
+
if (Array.isArray(to)) {
|
|
22
|
+
return to.map(normalizeEmailAddress).filter((v): v is { email: string } => Boolean(v));
|
|
23
|
+
}
|
|
24
|
+
const one = normalizeEmailAddress(to);
|
|
25
|
+
return one ? [one] : [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function registerSendTools(server: McpServer, guards: ToolGuards): void {
|
|
29
|
+
if (isToolEnabled({ name: 'send_email', mutates: true }, guards)) {
|
|
30
|
+
server.registerTool(
|
|
31
|
+
'send_email',
|
|
32
|
+
{
|
|
33
|
+
title: 'Send email',
|
|
34
|
+
description:
|
|
35
|
+
'Send a transactional email via Zyphr. Use either html/text OR templateId+templateData, not both.',
|
|
36
|
+
inputSchema: sendEmailShape,
|
|
37
|
+
},
|
|
38
|
+
async (args) => {
|
|
39
|
+
return runTool(async () => {
|
|
40
|
+
const zyphr = getZyphrClient();
|
|
41
|
+
return await zyphr.emails.sendEmail({
|
|
42
|
+
to: normalizeRecipients(args.to),
|
|
43
|
+
from: normalizeEmailAddress(args.from),
|
|
44
|
+
replyTo: normalizeEmailAddress(args.replyTo),
|
|
45
|
+
cc: args.cc,
|
|
46
|
+
bcc: args.bcc,
|
|
47
|
+
subject: args.subject,
|
|
48
|
+
html: args.html,
|
|
49
|
+
text: args.text,
|
|
50
|
+
templateId: args.templateId,
|
|
51
|
+
templateData: args.templateData,
|
|
52
|
+
tags: args.tags,
|
|
53
|
+
metadata: args.metadata,
|
|
54
|
+
subscriberId: args.subscriberId,
|
|
55
|
+
category: args.category,
|
|
56
|
+
scheduledAt: args.scheduledAt ? new Date(args.scheduledAt) : undefined,
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (isToolEnabled({ name: 'send_push', mutates: true }, guards)) {
|
|
64
|
+
server.registerTool(
|
|
65
|
+
'send_push',
|
|
66
|
+
{
|
|
67
|
+
title: 'Send push notification',
|
|
68
|
+
description:
|
|
69
|
+
'Send a push notification. Target one of: userId (all devices for a user), deviceId (specific device), subscriberId, or subscriberExternalId.',
|
|
70
|
+
inputSchema: sendPushShape,
|
|
71
|
+
},
|
|
72
|
+
async (args) => {
|
|
73
|
+
return runTool(async () => {
|
|
74
|
+
const zyphr = getZyphrClient();
|
|
75
|
+
return await zyphr.push.sendPush({
|
|
76
|
+
userId: args.userId,
|
|
77
|
+
deviceId: args.deviceId,
|
|
78
|
+
title: args.title,
|
|
79
|
+
body: args.body,
|
|
80
|
+
data: args.data,
|
|
81
|
+
badge: args.badge,
|
|
82
|
+
sound: args.sound,
|
|
83
|
+
imageUrl: args.imageUrl,
|
|
84
|
+
contentAvailable: args.contentAvailable,
|
|
85
|
+
tags: args.tags,
|
|
86
|
+
metadata: args.metadata,
|
|
87
|
+
collapseKey: args.collapseKey,
|
|
88
|
+
subscriberId: args.subscriberId,
|
|
89
|
+
subscriberExternalId: args.subscriberExternalId,
|
|
90
|
+
category: args.category,
|
|
91
|
+
force: args.force,
|
|
92
|
+
sendAt: args.sendAt ? new Date(args.sendAt) : undefined,
|
|
93
|
+
delay: args.delay,
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (isToolEnabled({ name: 'send_sms', mutates: true }, guards)) {
|
|
101
|
+
server.registerTool(
|
|
102
|
+
'send_sms',
|
|
103
|
+
{
|
|
104
|
+
title: 'Send SMS',
|
|
105
|
+
description: 'Send an SMS message via Zyphr. The recipient must be in E.164 format.',
|
|
106
|
+
inputSchema: sendSmsShape,
|
|
107
|
+
},
|
|
108
|
+
async (args) => {
|
|
109
|
+
return runTool(async () => {
|
|
110
|
+
const zyphr = getZyphrClient();
|
|
111
|
+
return await zyphr.sms.sendSms({
|
|
112
|
+
to: args.to,
|
|
113
|
+
from: args.from,
|
|
114
|
+
body: args.body,
|
|
115
|
+
subscriberId: args.subscriberId,
|
|
116
|
+
scheduledAt: args.scheduledAt ? new Date(args.scheduledAt) : undefined,
|
|
117
|
+
metadata: args.metadata,
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (isToolEnabled({ name: 'send_inbox_message', mutates: true }, guards)) {
|
|
125
|
+
server.registerTool(
|
|
126
|
+
'send_inbox_message',
|
|
127
|
+
{
|
|
128
|
+
title: 'Send in-app inbox message',
|
|
129
|
+
description: 'Deliver an in-app inbox notification to a Zyphr subscriber.',
|
|
130
|
+
inputSchema: sendInboxMessageShape,
|
|
131
|
+
},
|
|
132
|
+
async (args) => {
|
|
133
|
+
return runTool(async () => {
|
|
134
|
+
const zyphr = getZyphrClient();
|
|
135
|
+
return await zyphr.inbox.sendInApp({
|
|
136
|
+
subscriberId: args.subscriberId,
|
|
137
|
+
title: args.title,
|
|
138
|
+
body: args.body,
|
|
139
|
+
actionUrl: args.actionUrl,
|
|
140
|
+
actionLabel: args.actionLabel,
|
|
141
|
+
imageUrl: args.imageUrl,
|
|
142
|
+
icon: args.icon,
|
|
143
|
+
category: args.category,
|
|
144
|
+
priority: args.priority,
|
|
145
|
+
data: args.data,
|
|
146
|
+
tags: args.tags,
|
|
147
|
+
expiresAt: args.expiresAt ? new Date(args.expiresAt) : undefined,
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { getZyphrClient } from '../client.js';
|
|
3
|
+
import { isToolEnabled, type ToolGuards } from '../config.js';
|
|
4
|
+
import { runTool } from '../result.js';
|
|
5
|
+
import {
|
|
6
|
+
createSubscriberShape,
|
|
7
|
+
findSubscriberShape,
|
|
8
|
+
listSubscribersShape,
|
|
9
|
+
setSubscriberPreferencesShape,
|
|
10
|
+
updateSubscriberShape,
|
|
11
|
+
} from '../schemas.js';
|
|
12
|
+
|
|
13
|
+
export function registerSubscriberTools(server: McpServer, guards: ToolGuards): void {
|
|
14
|
+
if (isToolEnabled({ name: 'find_subscriber', mutates: false }, guards)) {
|
|
15
|
+
server.registerTool(
|
|
16
|
+
'find_subscriber',
|
|
17
|
+
{
|
|
18
|
+
title: 'Find subscriber by external ID',
|
|
19
|
+
description:
|
|
20
|
+
'Look up a subscriber by the external ID you assigned (typically your application user/customer ID).',
|
|
21
|
+
inputSchema: findSubscriberShape,
|
|
22
|
+
},
|
|
23
|
+
async (args) => {
|
|
24
|
+
return runTool(async () => {
|
|
25
|
+
const zyphr = getZyphrClient();
|
|
26
|
+
return await zyphr.subscribers.getSubscriberByExternalId(args.externalId);
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (isToolEnabled({ name: 'list_subscribers', mutates: false }, guards)) {
|
|
33
|
+
server.registerTool(
|
|
34
|
+
'list_subscribers',
|
|
35
|
+
{
|
|
36
|
+
title: 'List subscribers',
|
|
37
|
+
description: 'List subscribers, optionally filtered by status or email.',
|
|
38
|
+
inputSchema: listSubscribersShape,
|
|
39
|
+
},
|
|
40
|
+
async (args) => {
|
|
41
|
+
return runTool(async () => {
|
|
42
|
+
const zyphr = getZyphrClient();
|
|
43
|
+
return await zyphr.subscribers.listSubscribers(
|
|
44
|
+
args.status,
|
|
45
|
+
args.email,
|
|
46
|
+
args.limit,
|
|
47
|
+
args.offset,
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (isToolEnabled({ name: 'create_subscriber', mutates: true }, guards)) {
|
|
55
|
+
server.registerTool(
|
|
56
|
+
'create_subscriber',
|
|
57
|
+
{
|
|
58
|
+
title: 'Create subscriber',
|
|
59
|
+
description: 'Create a new subscriber. `externalId` must be unique within your account.',
|
|
60
|
+
inputSchema: createSubscriberShape,
|
|
61
|
+
},
|
|
62
|
+
async (args) => {
|
|
63
|
+
return runTool(async () => {
|
|
64
|
+
const zyphr = getZyphrClient();
|
|
65
|
+
return await zyphr.subscribers.createSubscriber({
|
|
66
|
+
externalId: args.externalId,
|
|
67
|
+
email: args.email,
|
|
68
|
+
phone: args.phone,
|
|
69
|
+
name: args.name,
|
|
70
|
+
avatarUrl: args.avatarUrl,
|
|
71
|
+
timezone: args.timezone,
|
|
72
|
+
locale: args.locale,
|
|
73
|
+
metadata: args.metadata,
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (isToolEnabled({ name: 'update_subscriber', mutates: true }, guards)) {
|
|
81
|
+
server.registerTool(
|
|
82
|
+
'update_subscriber',
|
|
83
|
+
{
|
|
84
|
+
title: 'Update subscriber',
|
|
85
|
+
description:
|
|
86
|
+
'Update a subscriber by Zyphr ID. Pass null for email/phone/name/avatarUrl to clear those fields.',
|
|
87
|
+
inputSchema: updateSubscriberShape,
|
|
88
|
+
},
|
|
89
|
+
async (args) => {
|
|
90
|
+
return runTool(async () => {
|
|
91
|
+
const zyphr = getZyphrClient();
|
|
92
|
+
return await zyphr.subscribers.updateSubscriber(args.id, {
|
|
93
|
+
email: args.email ?? undefined,
|
|
94
|
+
phone: args.phone ?? undefined,
|
|
95
|
+
name: args.name ?? undefined,
|
|
96
|
+
avatarUrl: args.avatarUrl ?? undefined,
|
|
97
|
+
timezone: args.timezone,
|
|
98
|
+
locale: args.locale,
|
|
99
|
+
metadata: args.metadata,
|
|
100
|
+
status: args.status,
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (isToolEnabled({ name: 'set_subscriber_preferences', mutates: true }, guards)) {
|
|
108
|
+
server.registerTool(
|
|
109
|
+
'set_subscriber_preferences',
|
|
110
|
+
{
|
|
111
|
+
title: 'Set subscriber preferences',
|
|
112
|
+
description:
|
|
113
|
+
'Set notification preferences for a subscriber. Each preference targets a category and/or channel and toggles `enabled`.',
|
|
114
|
+
inputSchema: setSubscriberPreferencesShape,
|
|
115
|
+
},
|
|
116
|
+
async (args) => {
|
|
117
|
+
return runTool(async () => {
|
|
118
|
+
const zyphr = getZyphrClient();
|
|
119
|
+
return await zyphr.subscribers.setSubscriberPreferences(args.id, {
|
|
120
|
+
preferences: args.preferences,
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { getZyphrClient } from '../client.js';
|
|
3
|
+
import { isToolEnabled, type ToolGuards } from '../config.js';
|
|
4
|
+
import { runTool } from '../result.js';
|
|
5
|
+
import {
|
|
6
|
+
createTemplateShape,
|
|
7
|
+
getTemplateShape,
|
|
8
|
+
listTemplatesShape,
|
|
9
|
+
renderTemplateShape,
|
|
10
|
+
} from '../schemas.js';
|
|
11
|
+
|
|
12
|
+
export function registerTemplateTools(server: McpServer, guards: ToolGuards): void {
|
|
13
|
+
if (isToolEnabled({ name: 'list_templates', mutates: false }, guards)) {
|
|
14
|
+
server.registerTool(
|
|
15
|
+
'list_templates',
|
|
16
|
+
{
|
|
17
|
+
title: 'List templates',
|
|
18
|
+
description: 'List notification templates in the account.',
|
|
19
|
+
inputSchema: listTemplatesShape,
|
|
20
|
+
},
|
|
21
|
+
async (args) => {
|
|
22
|
+
return runTool(async () => {
|
|
23
|
+
const zyphr = getZyphrClient();
|
|
24
|
+
return await zyphr.templates.listTemplates(args.limit, args.offset);
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (isToolEnabled({ name: 'get_template', mutates: false }, guards)) {
|
|
31
|
+
server.registerTool(
|
|
32
|
+
'get_template',
|
|
33
|
+
{
|
|
34
|
+
title: 'Get template',
|
|
35
|
+
description: 'Fetch a single template by ID.',
|
|
36
|
+
inputSchema: getTemplateShape,
|
|
37
|
+
},
|
|
38
|
+
async (args) => {
|
|
39
|
+
return runTool(async () => {
|
|
40
|
+
const zyphr = getZyphrClient();
|
|
41
|
+
return await zyphr.templates.getTemplate(args.id);
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (isToolEnabled({ name: 'render_template', mutates: false }, guards)) {
|
|
48
|
+
server.registerTool(
|
|
49
|
+
'render_template',
|
|
50
|
+
{
|
|
51
|
+
title: 'Render template',
|
|
52
|
+
description:
|
|
53
|
+
'Preview a template with the given variables WITHOUT sending. Returns the rendered subject/html/text so the AI can show the user what would be sent.',
|
|
54
|
+
inputSchema: renderTemplateShape,
|
|
55
|
+
},
|
|
56
|
+
async (args) => {
|
|
57
|
+
return runTool(async () => {
|
|
58
|
+
const zyphr = getZyphrClient();
|
|
59
|
+
return await zyphr.templates.renderTemplate(args.id, { variables: args.variables });
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (isToolEnabled({ name: 'create_template', mutates: true }, guards)) {
|
|
66
|
+
server.registerTool(
|
|
67
|
+
'create_template',
|
|
68
|
+
{
|
|
69
|
+
title: 'Create template',
|
|
70
|
+
description: 'Create a new notification template.',
|
|
71
|
+
inputSchema: createTemplateShape,
|
|
72
|
+
},
|
|
73
|
+
async (args) => {
|
|
74
|
+
return runTool(async () => {
|
|
75
|
+
const zyphr = getZyphrClient();
|
|
76
|
+
return await zyphr.templates.createTemplate({
|
|
77
|
+
name: args.name,
|
|
78
|
+
description: args.description,
|
|
79
|
+
subject: args.subject,
|
|
80
|
+
html: args.html,
|
|
81
|
+
text: args.text,
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|