@revealui/mcp 0.0.1-pre.0 → 0.1.1
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/LICENSE +22 -202
- package/LICENSE.commercial +112 -0
- package/README.md +263 -0
- package/dist/adapters/db.d.ts +46 -0
- package/dist/adapters/db.d.ts.map +1 -0
- package/dist/adapters/db.js +127 -0
- package/dist/adapters/db.js.map +1 -0
- package/dist/config/index.d.ts +11 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +18 -0
- package/dist/config/index.js.map +1 -0
- package/dist/contracts.d.ts +131 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +153 -0
- package/dist/contracts.js.map +1 -0
- package/dist/hypervisor.d.ts +132 -0
- package/dist/hypervisor.d.ts.map +1 -0
- package/dist/hypervisor.js +359 -0
- package/dist/hypervisor.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -10985
- package/dist/index.js.map +1 -1
- package/dist/servers/adapter.d.ts +209 -0
- package/dist/servers/adapter.d.ts.map +1 -0
- package/dist/servers/adapter.js +503 -0
- package/dist/servers/adapter.js.map +1 -0
- package/dist/servers/revealui-content.d.ts +21 -0
- package/dist/servers/revealui-content.d.ts.map +1 -0
- package/dist/servers/revealui-content.js +199 -0
- package/dist/servers/revealui-content.js.map +1 -0
- package/dist/servers/revealui-email.d.ts +18 -0
- package/dist/servers/revealui-email.d.ts.map +1 -0
- package/dist/servers/revealui-email.js +185 -0
- package/dist/servers/revealui-email.js.map +1 -0
- package/dist/servers/revealui-stripe.d.ts +19 -0
- package/dist/servers/revealui-stripe.d.ts.map +1 -0
- package/dist/servers/revealui-stripe.js +211 -0
- package/dist/servers/revealui-stripe.js.map +1 -0
- package/package.json +43 -68
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* RevealUI Content MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Model Context Protocol server that gives AI agents read/write access to
|
|
6
|
+
* RevealUI CMS content via the RevealUI REST API. Useful for agents that need
|
|
7
|
+
* to query site data, list content, manage users, or inspect deployment state.
|
|
8
|
+
*
|
|
9
|
+
* Environment:
|
|
10
|
+
* REVEALUI_API_URL — Base URL of the RevealUI API (e.g. https://api.mysite.com)
|
|
11
|
+
* REVEALUI_API_KEY — API key for authenticating with the RevealUI API
|
|
12
|
+
*
|
|
13
|
+
* Tools:
|
|
14
|
+
* revealui_list_sites — List all sites in the RevealUI instance
|
|
15
|
+
* revealui_list_content — List content entries for a site and collection
|
|
16
|
+
* revealui_get_content — Fetch a single content entry by ID
|
|
17
|
+
* revealui_list_users — List users (admin only)
|
|
18
|
+
* revealui_site_stats — Get user + content counts for a site
|
|
19
|
+
*/
|
|
20
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
21
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
22
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
23
|
+
import { logger } from '@revealui/core/observability/logger';
|
|
24
|
+
import { checkMcpLicense } from '../index.js';
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// API helpers
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
function apiHeaders(apiKey) {
|
|
29
|
+
return {
|
|
30
|
+
Authorization: `Bearer ${apiKey}`,
|
|
31
|
+
'Content-Type': 'application/json',
|
|
32
|
+
'User-Agent': 'RevealUI-MCP/1.0',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function apiGet(baseUrl, apiKey, path, params) {
|
|
36
|
+
const url = new URL(`${baseUrl}${path}`);
|
|
37
|
+
if (params) {
|
|
38
|
+
for (const [k, v] of Object.entries(params))
|
|
39
|
+
url.searchParams.set(k, v);
|
|
40
|
+
}
|
|
41
|
+
const res = await fetch(url.toString(), { headers: apiHeaders(apiKey) });
|
|
42
|
+
const body = await res.json();
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
throw new Error(body.error ??
|
|
45
|
+
body.message ??
|
|
46
|
+
`API ${res.status}`);
|
|
47
|
+
}
|
|
48
|
+
return body;
|
|
49
|
+
}
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Server
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
const server = new Server({ name: 'revealui-content', version: '1.0.0' }, { capabilities: { tools: {} } });
|
|
54
|
+
const TOOLS = [
|
|
55
|
+
{
|
|
56
|
+
name: 'revealui_list_sites',
|
|
57
|
+
description: 'List all sites registered in the RevealUI instance.',
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: 'object',
|
|
60
|
+
properties: {
|
|
61
|
+
limit: { type: 'number', description: 'Max results to return (default: 20)', default: 20 },
|
|
62
|
+
page: { type: 'number', description: 'Page number for pagination (default: 1)', default: 1 },
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'revealui_list_content',
|
|
68
|
+
description: 'List content entries for a site from a given collection (e.g. posts, pages, products).',
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: 'object',
|
|
71
|
+
properties: {
|
|
72
|
+
site_id: { type: 'string', description: 'Site ID to query content from' },
|
|
73
|
+
collection: { type: 'string', description: 'Collection slug (e.g. "posts", "pages", "products")' },
|
|
74
|
+
limit: { type: 'number', description: 'Max results (default: 20)', default: 20 },
|
|
75
|
+
page: { type: 'number', description: 'Page number (default: 1)', default: 1 },
|
|
76
|
+
status: { type: 'string', description: 'Filter by status: published, draft, archived' },
|
|
77
|
+
},
|
|
78
|
+
required: ['collection'],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'revealui_get_content',
|
|
83
|
+
description: 'Fetch a single content entry by its ID.',
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: 'object',
|
|
86
|
+
properties: {
|
|
87
|
+
collection: { type: 'string', description: 'Collection slug (e.g. "posts")' },
|
|
88
|
+
id: { type: 'string', description: 'Content entry ID' },
|
|
89
|
+
},
|
|
90
|
+
required: ['collection', 'id'],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'revealui_list_users',
|
|
95
|
+
description: 'List users registered in RevealUI. Requires admin API key. ' +
|
|
96
|
+
'Returns email, role, and account status.',
|
|
97
|
+
inputSchema: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
properties: {
|
|
100
|
+
site_id: { type: 'string', description: 'Filter users by site ID' },
|
|
101
|
+
limit: { type: 'number', description: 'Max results (default: 20)', default: 20 },
|
|
102
|
+
page: { type: 'number', description: 'Page number (default: 1)', default: 1 },
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'revealui_site_stats',
|
|
108
|
+
description: 'Get aggregate stats for a RevealUI site: user count, content count per collection, and license tier.',
|
|
109
|
+
inputSchema: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
properties: {
|
|
112
|
+
site_id: { type: 'string', description: 'Site ID to fetch stats for (omit for global stats)' },
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
118
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
119
|
+
const apiUrl = process.env.REVEALUI_API_URL?.replace(/\/$/, '');
|
|
120
|
+
const apiKey = process.env.REVEALUI_API_KEY;
|
|
121
|
+
if (!(apiUrl && apiKey)) {
|
|
122
|
+
return {
|
|
123
|
+
content: [{ type: 'text', text: 'Error: REVEALUI_API_URL and REVEALUI_API_KEY must be set' }],
|
|
124
|
+
isError: true,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
switch (request.params.name) {
|
|
129
|
+
case 'revealui_list_sites': {
|
|
130
|
+
const { limit = 20, page = 1 } = request.params.arguments;
|
|
131
|
+
const result = await apiGet(apiUrl, apiKey, '/api/sites', {
|
|
132
|
+
limit: String(limit),
|
|
133
|
+
page: String(page),
|
|
134
|
+
});
|
|
135
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
136
|
+
}
|
|
137
|
+
case 'revealui_list_content': {
|
|
138
|
+
const { site_id, collection, limit = 20, page = 1, status } = request.params.arguments;
|
|
139
|
+
const params = {
|
|
140
|
+
limit: String(limit),
|
|
141
|
+
page: String(page),
|
|
142
|
+
};
|
|
143
|
+
if (site_id)
|
|
144
|
+
params.siteId = site_id;
|
|
145
|
+
if (status)
|
|
146
|
+
params.status = status;
|
|
147
|
+
const result = await apiGet(apiUrl, apiKey, `/api/${collection}`, params);
|
|
148
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
149
|
+
}
|
|
150
|
+
case 'revealui_get_content': {
|
|
151
|
+
const { collection, id } = request.params.arguments;
|
|
152
|
+
const result = await apiGet(apiUrl, apiKey, `/api/${collection}/${id}`);
|
|
153
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
154
|
+
}
|
|
155
|
+
case 'revealui_list_users': {
|
|
156
|
+
const { site_id, limit = 20, page = 1 } = request.params.arguments;
|
|
157
|
+
const params = {
|
|
158
|
+
limit: String(limit),
|
|
159
|
+
page: String(page),
|
|
160
|
+
};
|
|
161
|
+
if (site_id)
|
|
162
|
+
params.siteId = site_id;
|
|
163
|
+
const result = await apiGet(apiUrl, apiKey, '/api/users', params);
|
|
164
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
165
|
+
}
|
|
166
|
+
case 'revealui_site_stats': {
|
|
167
|
+
const { site_id } = request.params.arguments;
|
|
168
|
+
const params = {};
|
|
169
|
+
if (site_id)
|
|
170
|
+
params.siteId = site_id;
|
|
171
|
+
const result = await apiGet(apiUrl, apiKey, '/api/health', params);
|
|
172
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
173
|
+
}
|
|
174
|
+
default:
|
|
175
|
+
return {
|
|
176
|
+
content: [{ type: 'text', text: `Error: Unknown tool: ${request.params.name}` }],
|
|
177
|
+
isError: true,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
return {
|
|
183
|
+
content: [{ type: 'text', text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
184
|
+
isError: true,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
async function main() {
|
|
189
|
+
if (!(await checkMcpLicense())) {
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
const transport = new StdioServerTransport();
|
|
193
|
+
await server.connect(transport);
|
|
194
|
+
}
|
|
195
|
+
main().catch((err) => {
|
|
196
|
+
logger.error('RevealUI Content MCP error', err instanceof Error ? err : new Error(String(err)));
|
|
197
|
+
process.exit(1);
|
|
198
|
+
});
|
|
199
|
+
//# sourceMappingURL=revealui-content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revealui-content.js","sourceRoot":"","sources":["../../src/servers/revealui-content.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAEL,qBAAqB,EACrB,sBAAsB,GAEvB,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAA;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE7C,8EAA8E;AAC9E,cAAc;AACd,8EAA8E;AAE9E,SAAS,UAAU,CAAC,MAAc;IAChC,OAAO;QACL,aAAa,EAAE,UAAU,MAAM,EAAE;QACjC,cAAc,EAAE,kBAAkB;QAClC,YAAY,EAAE,kBAAkB;KACjC,CAAA;AACH,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,OAAe,EAAE,MAAc,EAAE,IAAY,EAAE,MAA+B;IAClG,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,OAAO,GAAG,IAAI,EAAE,CAAC,CAAA;IACxC,IAAI,MAAM,EAAE,CAAC;QACX,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACzE,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IACxE,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;IAC7B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACZ,IAA6C,CAAC,KAAK;YACjD,IAA6B,CAAC,OAAO;YACtC,OAAO,GAAG,CAAC,MAAM,EAAE,CACtB,CAAA;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,8EAA8E;AAC9E,SAAS;AACT,8EAA8E;AAE9E,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,OAAO,EAAE,EAC9C,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAA;AAED,MAAM,KAAK,GAAW;IACpB;QACE,IAAI,EAAE,qBAAqB;QAC3B,WAAW,EAAE,qDAAqD;QAClE,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,qCAAqC,EAAE,OAAO,EAAE,EAAE,EAAE;gBAC1F,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,yCAAyC,EAAE,OAAO,EAAE,CAAC,EAAE;aAC7F;SACF;KACF;IACD;QACE,IAAI,EAAE,uBAAuB;QAC7B,WAAW,EACT,wFAAwF;QAC1F,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,+BAA+B,EAAE;gBACzE,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,qDAAqD,EAAE;gBAClG,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,2BAA2B,EAAE,OAAO,EAAE,EAAE,EAAE;gBAChF,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,0BAA0B,EAAE,OAAO,EAAE,CAAC,EAAE;gBAC7E,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,8CAA8C,EAAE;aACxF;YACD,QAAQ,EAAE,CAAC,YAAY,CAAC;SACzB;KACF;IACD;QACE,IAAI,EAAE,sBAAsB;QAC5B,WAAW,EAAE,yCAAyC;QACtD,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gCAAgC,EAAE;gBAC7E,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,kBAAkB,EAAE;aACxD;YACD,QAAQ,EAAE,CAAC,YAAY,EAAE,IAAI,CAAC;SAC/B;KACF;IACD;QACE,IAAI,EAAE,qBAAqB;QAC3B,WAAW,EACT,6DAA6D;YAC7D,0CAA0C;QAC5C,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,yBAAyB,EAAE;gBACnE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,2BAA2B,EAAE,OAAO,EAAE,EAAE,EAAE;gBAChF,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,0BAA0B,EAAE,OAAO,EAAE,CAAC,EAAE;aAC9E;SACF;KACF;IACD;QACE,IAAI,EAAE,qBAAqB;QAC3B,WAAW,EACT,sGAAsG;QACxG,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,oDAAoD,EAAE;aAC/F;SACF;KACF;CACF,CAAA;AAED,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;AAEhF,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAwB,EAAE,EAAE;IACjF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;IAC/D,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAA;IAE3C,IAAI,CAAC,CAAC,MAAM,IAAI,MAAM,CAAC,EAAE,CAAC;QACxB,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,0DAA0D,EAAE,CAAC;YAC7F,OAAO,EAAE,IAAI;SACd,CAAA;IACH,CAAC;IAED,IAAI,CAAC;QACH,QAAQ,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YAC5B,KAAK,qBAAqB,CAAC,CAAC,CAAC;gBAC3B,MAAM,EAAE,KAAK,GAAG,EAAE,EAAE,IAAI,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,SAG/C,CAAA;gBACD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE;oBACxD,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC;oBACpB,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC;iBACnB,CAAC,CAAA;gBACF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;YAC/E,CAAC;YAED,KAAK,uBAAuB,CAAC,CAAC,CAAC;gBAC7B,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,GAAG,EAAE,EAAE,IAAI,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,SAM5E,CAAA;gBACD,MAAM,MAAM,GAA2B;oBACrC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC;oBACpB,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC;iBACnB,CAAA;gBACD,IAAI,OAAO;oBAAE,MAAM,CAAC,MAAM,GAAG,OAAO,CAAA;gBACpC,IAAI,MAAM;oBAAE,MAAM,CAAC,MAAM,GAAG,MAAM,CAAA;gBAElC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,UAAU,EAAE,EAAE,MAAM,CAAC,CAAA;gBACzE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;YAC/E,CAAC;YAED,KAAK,sBAAsB,CAAC,CAAC,CAAC;gBAC5B,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,SAGzC,CAAA;gBACD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,UAAU,IAAI,EAAE,EAAE,CAAC,CAAA;gBACvE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;YAC/E,CAAC;YAED,KAAK,qBAAqB,CAAC,CAAC,CAAC;gBAC3B,MAAM,EAAE,OAAO,EAAE,KAAK,GAAG,EAAE,EAAE,IAAI,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,SAIxD,CAAA;gBACD,MAAM,MAAM,GAA2B;oBACrC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC;oBACpB,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC;iBACnB,CAAA;gBACD,IAAI,OAAO;oBAAE,MAAM,CAAC,MAAM,GAAG,OAAO,CAAA;gBAEpC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,CAAC,CAAA;gBACjE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;YAC/E,CAAC;YAED,KAAK,qBAAqB,CAAC,CAAC,CAAC;gBAC3B,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,SAAiC,CAAA;gBACpE,MAAM,MAAM,GAA2B,EAAE,CAAA;gBACzC,IAAI,OAAO;oBAAE,MAAM,CAAC,MAAM,GAAG,OAAO,CAAA;gBAEpC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,CAAC,CAAA;gBAClE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;YAC/E,CAAC;YAED;gBACE,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,wBAAwB,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;oBAChF,OAAO,EAAE,IAAI;iBACd,CAAA;QACL,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YAC/F,OAAO,EAAE,IAAI;SACd,CAAA;IACH,CAAC;AACH,CAAC,CAAC,CAAA;AAEF,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC,CAAC,MAAM,eAAe,EAAE,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;IAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;AACjC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,MAAM,CAAC,KAAK,CAAC,4BAA4B,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IAC/F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* RevealUI Email MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Model Context Protocol server that exposes email sending tools via Resend,
|
|
6
|
+
* RevealUI's transactional email provider. Lets AI agents send notification
|
|
7
|
+
* emails, digests, alerts, and templated messages on behalf of a RevealUI site.
|
|
8
|
+
*
|
|
9
|
+
* Environment:
|
|
10
|
+
* RESEND_API_KEY — Resend API key (re_...)
|
|
11
|
+
* REVEALUI_FROM_EMAIL — Default sender address (default: notifications@revealui.com)
|
|
12
|
+
*
|
|
13
|
+
* Tools:
|
|
14
|
+
* email_send — Send a single email (HTML or plain text)
|
|
15
|
+
* email_send_batch — Send up to 100 emails in one API call
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=revealui-email.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revealui-email.d.ts","sourceRoot":"","sources":["../../src/servers/revealui-email.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;GAcG"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* RevealUI Email MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Model Context Protocol server that exposes email sending tools via Resend,
|
|
6
|
+
* RevealUI's transactional email provider. Lets AI agents send notification
|
|
7
|
+
* emails, digests, alerts, and templated messages on behalf of a RevealUI site.
|
|
8
|
+
*
|
|
9
|
+
* Environment:
|
|
10
|
+
* RESEND_API_KEY — Resend API key (re_...)
|
|
11
|
+
* REVEALUI_FROM_EMAIL — Default sender address (default: notifications@revealui.com)
|
|
12
|
+
*
|
|
13
|
+
* Tools:
|
|
14
|
+
* email_send — Send a single email (HTML or plain text)
|
|
15
|
+
* email_send_batch — Send up to 100 emails in one API call
|
|
16
|
+
*/
|
|
17
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
18
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
19
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
20
|
+
import { logger } from '@revealui/core/observability/logger';
|
|
21
|
+
import { checkMcpLicense } from '../index.js';
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Resend REST helpers
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
const RESEND_BASE = 'https://api.resend.com';
|
|
26
|
+
const DEFAULT_FROM = 'RevealUI <notifications@revealui.com>';
|
|
27
|
+
async function resendPost(apiKey, path, body) {
|
|
28
|
+
const res = await fetch(`${RESEND_BASE}${path}`, {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: {
|
|
31
|
+
Authorization: `Bearer ${apiKey}`,
|
|
32
|
+
'Content-Type': 'application/json',
|
|
33
|
+
'User-Agent': 'RevealUI-MCP/1.0',
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify(body),
|
|
36
|
+
});
|
|
37
|
+
const data = await res.json();
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
throw new Error(data.message ??
|
|
40
|
+
data.name ??
|
|
41
|
+
`Resend ${res.status}`);
|
|
42
|
+
}
|
|
43
|
+
return data;
|
|
44
|
+
}
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Server
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
const server = new Server({ name: 'revealui-email', version: '1.0.0' }, { capabilities: { tools: {} } });
|
|
49
|
+
const TOOLS = [
|
|
50
|
+
{
|
|
51
|
+
name: 'email_send',
|
|
52
|
+
description: 'Send a single transactional email via RevealUI\'s Resend account. ' +
|
|
53
|
+
'Provide either html or text (or both). Returns the Resend message ID.',
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
to: {
|
|
58
|
+
oneOf: [
|
|
59
|
+
{ type: 'string', description: 'Single recipient email' },
|
|
60
|
+
{ type: 'array', items: { type: 'string' }, description: 'Multiple recipients' },
|
|
61
|
+
],
|
|
62
|
+
description: 'Recipient email address(es)',
|
|
63
|
+
},
|
|
64
|
+
subject: { type: 'string', description: 'Email subject line' },
|
|
65
|
+
html: { type: 'string', description: 'HTML body of the email' },
|
|
66
|
+
text: { type: 'string', description: 'Plain-text fallback body' },
|
|
67
|
+
from: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
description: `Sender address (default: ${DEFAULT_FROM})`,
|
|
70
|
+
},
|
|
71
|
+
reply_to: { type: 'string', description: 'Reply-to address' },
|
|
72
|
+
},
|
|
73
|
+
required: ['to', 'subject'],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'email_send_batch',
|
|
78
|
+
description: 'Send up to 100 emails in a single Resend batch request. ' +
|
|
79
|
+
'Each email in the batch can have its own to/subject/html/text.',
|
|
80
|
+
inputSchema: {
|
|
81
|
+
type: 'object',
|
|
82
|
+
properties: {
|
|
83
|
+
emails: {
|
|
84
|
+
type: 'array',
|
|
85
|
+
description: 'Array of email objects to send',
|
|
86
|
+
items: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
properties: {
|
|
89
|
+
to: { type: 'string', description: 'Recipient email' },
|
|
90
|
+
subject: { type: 'string', description: 'Subject line' },
|
|
91
|
+
html: { type: 'string', description: 'HTML body' },
|
|
92
|
+
text: { type: 'string', description: 'Plain text body' },
|
|
93
|
+
from: { type: 'string', description: 'Sender address (defaults to REVEALUI_FROM_EMAIL)' },
|
|
94
|
+
},
|
|
95
|
+
required: ['to', 'subject'],
|
|
96
|
+
},
|
|
97
|
+
maxItems: 100,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
required: ['emails'],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
105
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
106
|
+
const apiKey = process.env.RESEND_API_KEY;
|
|
107
|
+
if (!apiKey) {
|
|
108
|
+
return {
|
|
109
|
+
content: [{ type: 'text', text: 'Error: RESEND_API_KEY is not set' }],
|
|
110
|
+
isError: true,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const fromAddress = process.env.REVEALUI_FROM_EMAIL ?? DEFAULT_FROM;
|
|
114
|
+
try {
|
|
115
|
+
switch (request.params.name) {
|
|
116
|
+
case 'email_send': {
|
|
117
|
+
const { to, subject, html, text, from = fromAddress, reply_to } = request.params.arguments;
|
|
118
|
+
if (!(html ?? text)) {
|
|
119
|
+
return {
|
|
120
|
+
content: [{ type: 'text', text: 'Error: Provide at least one of html or text' }],
|
|
121
|
+
isError: true,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const payload = { from, to, subject };
|
|
125
|
+
if (html)
|
|
126
|
+
payload.html = html;
|
|
127
|
+
if (text)
|
|
128
|
+
payload.text = text;
|
|
129
|
+
if (reply_to)
|
|
130
|
+
payload.reply_to = reply_to;
|
|
131
|
+
// Tag all RevealUI MCP emails for filtering in Resend dashboard
|
|
132
|
+
payload.tags = [{ name: 'source', value: 'revealui-mcp' }];
|
|
133
|
+
const result = await resendPost(apiKey, '/emails', payload);
|
|
134
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
135
|
+
}
|
|
136
|
+
case 'email_send_batch': {
|
|
137
|
+
const { emails } = request.params.arguments;
|
|
138
|
+
if (emails.length > 100) {
|
|
139
|
+
return {
|
|
140
|
+
content: [{ type: 'text', text: 'Error: Batch size cannot exceed 100 emails' }],
|
|
141
|
+
isError: true,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const batch = emails.map((e) => {
|
|
145
|
+
const payload = {
|
|
146
|
+
from: e.from ?? fromAddress,
|
|
147
|
+
to: e.to,
|
|
148
|
+
subject: e.subject,
|
|
149
|
+
tags: [{ name: 'source', value: 'revealui-mcp' }],
|
|
150
|
+
};
|
|
151
|
+
if (e.html)
|
|
152
|
+
payload.html = e.html;
|
|
153
|
+
if (e.text)
|
|
154
|
+
payload.text = e.text;
|
|
155
|
+
return payload;
|
|
156
|
+
});
|
|
157
|
+
const result = await resendPost(apiKey, '/emails/batch', batch);
|
|
158
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
159
|
+
}
|
|
160
|
+
default:
|
|
161
|
+
return {
|
|
162
|
+
content: [{ type: 'text', text: `Error: Unknown tool: ${request.params.name}` }],
|
|
163
|
+
isError: true,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
return {
|
|
169
|
+
content: [{ type: 'text', text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
170
|
+
isError: true,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
async function main() {
|
|
175
|
+
if (!(await checkMcpLicense())) {
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
const transport = new StdioServerTransport();
|
|
179
|
+
await server.connect(transport);
|
|
180
|
+
}
|
|
181
|
+
main().catch((err) => {
|
|
182
|
+
logger.error('RevealUI Email MCP error', err instanceof Error ? err : new Error(String(err)));
|
|
183
|
+
process.exit(1);
|
|
184
|
+
});
|
|
185
|
+
//# sourceMappingURL=revealui-email.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revealui-email.js","sourceRoot":"","sources":["../../src/servers/revealui-email.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAEL,qBAAqB,EACrB,sBAAsB,GAEvB,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAA;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE7C,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E,MAAM,WAAW,GAAG,wBAAwB,CAAA;AAC5C,MAAM,YAAY,GAAG,uCAAuC,CAAA;AAY5D,KAAK,UAAU,UAAU,CAAC,MAAc,EAAE,IAAY,EAAE,IAAa;IACnE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,WAAW,GAAG,IAAI,EAAE,EAAE;QAC/C,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,MAAM,EAAE;YACjC,cAAc,EAAE,kBAAkB;YAClC,YAAY,EAAE,kBAAkB;SACjC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;KAC3B,CAAC,CAAA;IACF,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;IAC7B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACZ,IAA4C,CAAC,OAAO;YAClD,IAA0B,CAAC,IAAI;YAChC,UAAU,GAAG,CAAC,MAAM,EAAE,CACzB,CAAA;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,8EAA8E;AAC9E,SAAS;AACT,8EAA8E;AAE9E,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,OAAO,EAAE,EAC5C,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAA;AAED,MAAM,KAAK,GAAW;IACpB;QACE,IAAI,EAAE,YAAY;QAClB,WAAW,EACT,oEAAoE;YACpE,uEAAuE;QACzE,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,EAAE,EAAE;oBACF,KAAK,EAAE;wBACL,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,wBAAwB,EAAE;wBACzD,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,qBAAqB,EAAE;qBACjF;oBACD,WAAW,EAAE,6BAA6B;iBAC3C;gBACD,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,oBAAoB,EAAE;gBAC9D,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,wBAAwB,EAAE;gBAC/D,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,0BAA0B,EAAE;gBACjE,IAAI,EAAE;oBACJ,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,4BAA4B,YAAY,GAAG;iBACzD;gBACD,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,kBAAkB,EAAE;aAC9D;YACD,QAAQ,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC;SAC5B;KACF;IACD;QACE,IAAI,EAAE,kBAAkB;QACxB,WAAW,EACT,0DAA0D;YAC1D,gEAAgE;QAClE,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,MAAM,EAAE;oBACN,IAAI,EAAE,OAAO;oBACb,WAAW,EAAE,gCAAgC;oBAC7C,KAAK,EAAE;wBACL,IAAI,EAAE,QAAQ;wBACd,UAAU,EAAE;4BACV,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,iBAAiB,EAAE;4BACtD,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE;4BACxD,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE;4BAClD,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,iBAAiB,EAAE;4BACxD,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,kDAAkD,EAAE;yBAC1F;wBACD,QAAQ,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC;qBAC5B;oBACD,QAAQ,EAAE,GAAG;iBACd;aACF;YACD,QAAQ,EAAE,CAAC,QAAQ,CAAC;SACrB;KACF;CACF,CAAA;AAED,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;AAEhF,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAwB,EAAE,EAAE;IACjF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAA;IACzC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,kCAAkC,EAAE,CAAC;YACrE,OAAO,EAAE,IAAI;SACd,CAAA;IACH,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,YAAY,CAAA;IAEnE,IAAI,CAAC;QACH,QAAQ,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YAC5B,KAAK,YAAY,CAAC,CAAC,CAAC;gBAClB,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,GAAG,WAAW,EAAE,QAAQ,EAAE,GAC7D,OAAO,CAAC,MAAM,CAAC,SAOd,CAAA;gBAEH,IAAI,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;oBACpB,OAAO;wBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,6CAA6C,EAAE,CAAC;wBAChF,OAAO,EAAE,IAAI;qBACd,CAAA;gBACH,CAAC;gBAED,MAAM,OAAO,GAAkB,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,CAAA;gBACpD,IAAI,IAAI;oBAAE,OAAO,CAAC,IAAI,GAAG,IAAI,CAAA;gBAC7B,IAAI,IAAI;oBAAE,OAAO,CAAC,IAAI,GAAG,IAAI,CAAA;gBAC7B,IAAI,QAAQ;oBAAE,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAA;gBACzC,gEAAgE;gBAChE,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAA;gBAE1D,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,CAAA;gBAC3D,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;YAC/E,CAAC;YAED,KAAK,kBAAkB,CAAC,CAAC,CAAC;gBACxB,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,SAQjC,CAAA;gBAED,IAAI,MAAM,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;oBACxB,OAAO;wBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,4CAA4C,EAAE,CAAC;wBAC/E,OAAO,EAAE,IAAI;qBACd,CAAA;gBACH,CAAC;gBAED,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;oBAC7B,MAAM,OAAO,GAAkB;wBAC7B,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,WAAW;wBAC3B,EAAE,EAAE,CAAC,CAAC,EAAE;wBACR,OAAO,EAAE,CAAC,CAAC,OAAO;wBAClB,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC;qBAClD,CAAA;oBACD,IAAI,CAAC,CAAC,IAAI;wBAAE,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAA;oBACjC,IAAI,CAAC,CAAC,IAAI;wBAAE,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAA;oBACjC,OAAO,OAAO,CAAA;gBAChB,CAAC,CAAC,CAAA;gBAEF,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,CAAC,CAAA;gBAC/D,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;YAC/E,CAAC;YAED;gBACE,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,wBAAwB,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;oBAChF,OAAO,EAAE,IAAI;iBACd,CAAA;QACL,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YAC/F,OAAO,EAAE,IAAI;SACd,CAAA;IACH,CAAC;AACH,CAAC,CAAC,CAAA;AAEF,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC,CAAC,MAAM,eAAe,EAAE,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;IAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;AACjC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,MAAM,CAAC,KAAK,CAAC,0BAA0B,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IAC7F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* RevealUI Stripe MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Model Context Protocol server that exposes Stripe payment management tools
|
|
6
|
+
* scoped to RevealUI's subscription model. Gives AI agents structured access
|
|
7
|
+
* to customer, subscription, and payment intent data in RevealUI's Stripe account.
|
|
8
|
+
*
|
|
9
|
+
* Environment:
|
|
10
|
+
* STRIPE_SECRET_KEY — Stripe secret key (sk_live_... or sk_test_...)
|
|
11
|
+
*
|
|
12
|
+
* Tools:
|
|
13
|
+
* stripe_create_payment_intent — Create a one-time payment intent
|
|
14
|
+
* stripe_list_customers — List customers, with optional email filter
|
|
15
|
+
* stripe_get_customer — Fetch a customer and their active subscription
|
|
16
|
+
* stripe_list_subscriptions — List subscriptions (all or by customer/status)
|
|
17
|
+
*/
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=revealui-stripe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revealui-stripe.d.ts","sourceRoot":"","sources":["../../src/servers/revealui-stripe.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;GAeG"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* RevealUI Stripe MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Model Context Protocol server that exposes Stripe payment management tools
|
|
6
|
+
* scoped to RevealUI's subscription model. Gives AI agents structured access
|
|
7
|
+
* to customer, subscription, and payment intent data in RevealUI's Stripe account.
|
|
8
|
+
*
|
|
9
|
+
* Environment:
|
|
10
|
+
* STRIPE_SECRET_KEY — Stripe secret key (sk_live_... or sk_test_...)
|
|
11
|
+
*
|
|
12
|
+
* Tools:
|
|
13
|
+
* stripe_create_payment_intent — Create a one-time payment intent
|
|
14
|
+
* stripe_list_customers — List customers, with optional email filter
|
|
15
|
+
* stripe_get_customer — Fetch a customer and their active subscription
|
|
16
|
+
* stripe_list_subscriptions — List subscriptions (all or by customer/status)
|
|
17
|
+
*/
|
|
18
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
19
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
20
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
21
|
+
import { logger } from '@revealui/core/observability/logger';
|
|
22
|
+
import { checkMcpLicense } from '../index.js';
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Stripe REST helpers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
const STRIPE_BASE = 'https://api.stripe.com/v1';
|
|
27
|
+
function stripeHeaders(secretKey) {
|
|
28
|
+
return {
|
|
29
|
+
Authorization: `Bearer ${secretKey}`,
|
|
30
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
31
|
+
'Stripe-Version': '2025-03-31.basil',
|
|
32
|
+
'User-Agent': 'RevealUI-MCP/1.0',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function stripeGet(path, secretKey, params) {
|
|
36
|
+
const url = new URL(`${STRIPE_BASE}${path}`);
|
|
37
|
+
if (params) {
|
|
38
|
+
for (const [k, v] of Object.entries(params))
|
|
39
|
+
url.searchParams.set(k, v);
|
|
40
|
+
}
|
|
41
|
+
const res = await fetch(url.toString(), { headers: stripeHeaders(secretKey) });
|
|
42
|
+
const body = await res.json();
|
|
43
|
+
if (!res.ok)
|
|
44
|
+
throw new Error(body.error?.message ?? `Stripe ${res.status}`);
|
|
45
|
+
return body;
|
|
46
|
+
}
|
|
47
|
+
async function stripePost(path, secretKey, data) {
|
|
48
|
+
const res = await fetch(`${STRIPE_BASE}${path}`, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: stripeHeaders(secretKey),
|
|
51
|
+
body: new URLSearchParams(data).toString(),
|
|
52
|
+
});
|
|
53
|
+
const body = await res.json();
|
|
54
|
+
if (!res.ok)
|
|
55
|
+
throw new Error(body.error?.message ?? `Stripe ${res.status}`);
|
|
56
|
+
return body;
|
|
57
|
+
}
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Server
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
const server = new Server({ name: 'revealui-stripe', version: '1.0.0' }, { capabilities: { tools: {} } });
|
|
62
|
+
const TOOLS = [
|
|
63
|
+
{
|
|
64
|
+
name: 'stripe_create_payment_intent',
|
|
65
|
+
description: 'Create a Stripe PaymentIntent for a one-time charge in RevealUI. ' +
|
|
66
|
+
'Amount is in the smallest currency unit (cents for USD).',
|
|
67
|
+
inputSchema: {
|
|
68
|
+
type: 'object',
|
|
69
|
+
properties: {
|
|
70
|
+
amount: { type: 'number', description: 'Amount in smallest currency unit (e.g. 2999 = $29.99)' },
|
|
71
|
+
currency: { type: 'string', description: 'ISO 4217 currency code (default: usd)', default: 'usd' },
|
|
72
|
+
customer_id: { type: 'string', description: 'Stripe customer ID to attach the payment to' },
|
|
73
|
+
description: { type: 'string', description: 'Human-readable description of the charge' },
|
|
74
|
+
},
|
|
75
|
+
required: ['amount'],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'stripe_list_customers',
|
|
80
|
+
description: 'List Stripe customers in the RevealUI account. Optionally filter by email.',
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {
|
|
84
|
+
email: { type: 'string', description: 'Filter by exact email address' },
|
|
85
|
+
limit: { type: 'number', description: 'Number of results (1-100, default: 20)', default: 20 },
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'stripe_get_customer',
|
|
91
|
+
description: 'Fetch a Stripe customer by ID, including their active RevealUI subscription tier.',
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: 'object',
|
|
94
|
+
properties: {
|
|
95
|
+
customer_id: { type: 'string', description: 'Stripe customer ID (cus_...)' },
|
|
96
|
+
},
|
|
97
|
+
required: ['customer_id'],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'stripe_list_subscriptions',
|
|
102
|
+
description: 'List Stripe subscriptions for RevealUI. Can filter by customer and/or status.',
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: {
|
|
106
|
+
customer_id: { type: 'string', description: 'Filter to a specific customer (cus_...)' },
|
|
107
|
+
status: {
|
|
108
|
+
type: 'string',
|
|
109
|
+
description: 'Filter by status: active, canceled, past_due, trialing, all (default: active)',
|
|
110
|
+
default: 'active',
|
|
111
|
+
},
|
|
112
|
+
limit: { type: 'number', description: 'Number of results (1-100, default: 20)', default: 20 },
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
118
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
119
|
+
const secretKey = process.env.STRIPE_SECRET_KEY;
|
|
120
|
+
if (!secretKey) {
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: 'text', text: 'Error: STRIPE_SECRET_KEY is not set' }],
|
|
123
|
+
isError: true,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
switch (request.params.name) {
|
|
128
|
+
case 'stripe_create_payment_intent': {
|
|
129
|
+
const { amount, currency = 'usd', customer_id, description } = request.params.arguments;
|
|
130
|
+
const data = {
|
|
131
|
+
amount: String(Math.round(amount)),
|
|
132
|
+
currency,
|
|
133
|
+
};
|
|
134
|
+
if (customer_id)
|
|
135
|
+
data.customer = customer_id;
|
|
136
|
+
if (description)
|
|
137
|
+
data.description = description;
|
|
138
|
+
const intent = await stripePost('/payment_intents', secretKey, data);
|
|
139
|
+
return {
|
|
140
|
+
content: [{ type: 'text', text: JSON.stringify(intent, null, 2) }],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
case 'stripe_list_customers': {
|
|
144
|
+
const { email, limit = 20 } = request.params.arguments;
|
|
145
|
+
const params = { limit: String(Math.min(limit, 100)) };
|
|
146
|
+
if (email)
|
|
147
|
+
params.email = email;
|
|
148
|
+
const result = await stripeGet('/customers', secretKey, params);
|
|
149
|
+
return {
|
|
150
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
case 'stripe_get_customer': {
|
|
154
|
+
const { customer_id } = request.params.arguments;
|
|
155
|
+
const [customer, subscriptions] = await Promise.all([
|
|
156
|
+
stripeGet(`/customers/${customer_id}`, secretKey),
|
|
157
|
+
stripeGet('/subscriptions', secretKey, {
|
|
158
|
+
customer: customer_id,
|
|
159
|
+
status: 'active',
|
|
160
|
+
limit: '5',
|
|
161
|
+
'expand[]': 'data.items.data.price.product',
|
|
162
|
+
}),
|
|
163
|
+
]);
|
|
164
|
+
return {
|
|
165
|
+
content: [
|
|
166
|
+
{
|
|
167
|
+
type: 'text',
|
|
168
|
+
text: JSON.stringify({ customer, subscriptions }, null, 2),
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
case 'stripe_list_subscriptions': {
|
|
174
|
+
const { customer_id, status = 'active', limit = 20 } = request.params.arguments;
|
|
175
|
+
const params = {
|
|
176
|
+
limit: String(Math.min(limit, 100)),
|
|
177
|
+
status,
|
|
178
|
+
};
|
|
179
|
+
if (customer_id)
|
|
180
|
+
params.customer = customer_id;
|
|
181
|
+
const result = await stripeGet('/subscriptions', secretKey, params);
|
|
182
|
+
return {
|
|
183
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
default:
|
|
187
|
+
return {
|
|
188
|
+
content: [{ type: 'text', text: `Error: Unknown tool: ${request.params.name}` }],
|
|
189
|
+
isError: true,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
return {
|
|
195
|
+
content: [{ type: 'text', text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
196
|
+
isError: true,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
async function main() {
|
|
201
|
+
if (!(await checkMcpLicense())) {
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
const transport = new StdioServerTransport();
|
|
205
|
+
await server.connect(transport);
|
|
206
|
+
}
|
|
207
|
+
main().catch((err) => {
|
|
208
|
+
logger.error('RevealUI Stripe MCP error', err instanceof Error ? err : new Error(String(err)));
|
|
209
|
+
process.exit(1);
|
|
210
|
+
});
|
|
211
|
+
//# sourceMappingURL=revealui-stripe.js.map
|