@vettly/mcp 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/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # @vettly/mcp
2
+
3
+ MCP (Model Context Protocol) server for Vettly content moderation API. Enables AI assistants like Claude to moderate content, validate policies, and review moderation decisions.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @vettly/mcp
9
+ # or
10
+ npx @vettly/mcp
11
+ ```
12
+
13
+ ## Configuration
14
+
15
+ ### Claude Desktop
16
+
17
+ Add to your `claude_desktop_config.json`:
18
+
19
+ ```json
20
+ {
21
+ "mcpServers": {
22
+ "vettly": {
23
+ "command": "npx",
24
+ "args": ["-y", "@vettly/mcp"],
25
+ "env": {
26
+ "VETTLY_API_KEY": "vettly_live_xxx"
27
+ }
28
+ }
29
+ }
30
+ }
31
+ ```
32
+
33
+ ### Environment Variables
34
+
35
+ - `VETTLY_API_KEY` (required) - Your Vettly API key
36
+ - `VETTLY_API_URL` (optional) - API URL (defaults to `https://api.vettly.dev`)
37
+
38
+ ## Available Tools
39
+
40
+ ### `moderate_content`
41
+
42
+ Check content against a Vettly moderation policy.
43
+
44
+ **Parameters:**
45
+ - `content` (required) - The content to moderate
46
+ - `policyId` (required) - The policy ID to use
47
+ - `contentType` (optional) - `text`, `image`, or `video` (default: `text`)
48
+
49
+ **Example:**
50
+ ```
51
+ Moderate this text: "Hello world" using policy "default-policy"
52
+ ```
53
+
54
+ ### `validate_policy`
55
+
56
+ Validate a policy YAML without saving it.
57
+
58
+ **Parameters:**
59
+ - `yamlContent` (required) - The YAML policy content to validate
60
+
61
+ **Example:**
62
+ ```
63
+ Validate this policy YAML:
64
+ name: my-policy
65
+ rules:
66
+ - category: hate_speech
67
+ threshold: 0.7
68
+ action: block
69
+ ```
70
+
71
+ ### `list_policies`
72
+
73
+ List all available moderation policies in your account.
74
+
75
+ **Parameters:** None
76
+
77
+ ### `get_usage_stats`
78
+
79
+ Get usage statistics including request counts, costs, and moderation outcomes.
80
+
81
+ **Parameters:**
82
+ - `days` (optional) - Number of days to include (1-365, default: 30)
83
+
84
+ ### `get_recent_decisions`
85
+
86
+ Get recent moderation decisions with optional filtering.
87
+
88
+ **Parameters:**
89
+ - `limit` (optional) - Number of decisions to return (1-50, default: 10)
90
+ - `flagged` (optional) - Filter by flagged status
91
+ - `policyId` (optional) - Filter by policy ID
92
+ - `contentType` (optional) - Filter by content type
93
+
94
+ ## Resources
95
+
96
+ ### `vettly://policies`
97
+
98
+ Read-only list of all available moderation policies.
99
+
100
+ ### `vettly://policies/{policyId}`
101
+
102
+ Read-only access to a specific policy's YAML configuration.
103
+
104
+ ## Programmatic Usage
105
+
106
+ ```typescript
107
+ import { createVettlyMcpServer } from '@vettly/mcp';
108
+
109
+ const server = createVettlyMcpServer({
110
+ apiKey: process.env.VETTLY_API_KEY!,
111
+ apiUrl: 'https://api.vettly.dev', // optional
112
+ });
113
+
114
+ // Connect to your transport
115
+ server.connect(transport);
116
+ ```
117
+
118
+ ## Links
119
+
120
+ - [Vettly Documentation](https://docs.vettly.dev)
121
+ - [Get an API Key](https://vettly.dev)
122
+ - [MCP Specification](https://modelcontextprotocol.io)
@@ -0,0 +1,365 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // src/bin/vettly-mcp.ts
5
+ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
6
+
7
+ // src/server.ts
8
+ var import_mcp2 = require("@modelcontextprotocol/sdk/server/mcp.js");
9
+ var import_sdk = require("@vettly/sdk");
10
+
11
+ // src/tools/moderate.ts
12
+ var import_zod = require("zod");
13
+ var ModerateInputSchema = import_zod.z.object({
14
+ content: import_zod.z.string().min(1).max(1e5).describe("The content to moderate (text content, or URL for images/video)"),
15
+ policyId: import_zod.z.string().min(1).describe("The policy ID to use for moderation"),
16
+ contentType: import_zod.z.enum(["text", "image", "video"]).default("text").describe("Type of content being moderated")
17
+ });
18
+ function registerModerateTool(server, client) {
19
+ server.tool(
20
+ "moderate_content",
21
+ "Check text, image, or video content against a Vettly moderation policy. Returns safety assessment with category scores.",
22
+ {
23
+ content: ModerateInputSchema.shape.content,
24
+ policyId: ModerateInputSchema.shape.policyId,
25
+ contentType: ModerateInputSchema.shape.contentType
26
+ },
27
+ async (args) => {
28
+ const input = ModerateInputSchema.parse(args);
29
+ try {
30
+ const result = await client.check({
31
+ content: input.content,
32
+ policyId: input.policyId,
33
+ contentType: input.contentType
34
+ });
35
+ return {
36
+ content: [
37
+ {
38
+ type: "text",
39
+ text: JSON.stringify(
40
+ {
41
+ decisionId: result.decisionId,
42
+ safe: result.safe,
43
+ flagged: result.flagged,
44
+ action: result.action,
45
+ categories: result.categories,
46
+ provider: result.provider,
47
+ latency: result.latency,
48
+ cost: result.cost
49
+ },
50
+ null,
51
+ 2
52
+ )
53
+ }
54
+ ]
55
+ };
56
+ } catch (error) {
57
+ return {
58
+ content: [
59
+ {
60
+ type: "text",
61
+ text: `Moderation failed: ${error instanceof Error ? error.message : "Unknown error"}`
62
+ }
63
+ ],
64
+ isError: true
65
+ };
66
+ }
67
+ }
68
+ );
69
+ }
70
+
71
+ // src/tools/validate.ts
72
+ var import_zod2 = require("zod");
73
+ var ValidateInputSchema = import_zod2.z.object({
74
+ yamlContent: import_zod2.z.string().min(1).describe("The YAML policy content to validate")
75
+ });
76
+ function registerValidateTool(server, client) {
77
+ server.tool(
78
+ "validate_policy",
79
+ "Validate a Vettly policy YAML without saving it. Returns validation results with any syntax or configuration errors.",
80
+ {
81
+ yamlContent: ValidateInputSchema.shape.yamlContent
82
+ },
83
+ async (args) => {
84
+ const input = ValidateInputSchema.parse(args);
85
+ try {
86
+ const result = await client.validatePolicy(input.yamlContent);
87
+ return {
88
+ content: [
89
+ {
90
+ type: "text",
91
+ text: JSON.stringify(result, null, 2)
92
+ }
93
+ ]
94
+ };
95
+ } catch (error) {
96
+ return {
97
+ content: [
98
+ {
99
+ type: "text",
100
+ text: `Validation failed: ${error instanceof Error ? error.message : "Unknown error"}`
101
+ }
102
+ ],
103
+ isError: true
104
+ };
105
+ }
106
+ }
107
+ );
108
+ }
109
+
110
+ // src/tools/list-policies.ts
111
+ function registerListPoliciesTool(server, client) {
112
+ server.tool(
113
+ "list_policies",
114
+ "List all moderation policies available in your Vettly account.",
115
+ {},
116
+ async () => {
117
+ try {
118
+ const result = await client.listPolicies();
119
+ return {
120
+ content: [
121
+ {
122
+ type: "text",
123
+ text: JSON.stringify(result, null, 2)
124
+ }
125
+ ]
126
+ };
127
+ } catch (error) {
128
+ return {
129
+ content: [
130
+ {
131
+ type: "text",
132
+ text: `Failed to list policies: ${error instanceof Error ? error.message : "Unknown error"}`
133
+ }
134
+ ],
135
+ isError: true
136
+ };
137
+ }
138
+ }
139
+ );
140
+ }
141
+
142
+ // src/tools/usage-stats.ts
143
+ var import_zod3 = require("zod");
144
+ var UsageStatsInputSchema = import_zod3.z.object({
145
+ days: import_zod3.z.number().min(1).max(365).default(30).describe("Number of days to include in statistics")
146
+ });
147
+ function registerUsageStatsTool(server, client) {
148
+ server.tool(
149
+ "get_usage_stats",
150
+ "Get usage statistics for your Vettly account including request counts, costs, and moderation outcomes.",
151
+ {
152
+ days: UsageStatsInputSchema.shape.days
153
+ },
154
+ async (args) => {
155
+ const input = UsageStatsInputSchema.parse(args);
156
+ try {
157
+ const result = await client.getUsageStats(input.days);
158
+ return {
159
+ content: [
160
+ {
161
+ type: "text",
162
+ text: JSON.stringify(result, null, 2)
163
+ }
164
+ ]
165
+ };
166
+ } catch (error) {
167
+ return {
168
+ content: [
169
+ {
170
+ type: "text",
171
+ text: `Failed to get usage stats: ${error instanceof Error ? error.message : "Unknown error"}`
172
+ }
173
+ ],
174
+ isError: true
175
+ };
176
+ }
177
+ }
178
+ );
179
+ }
180
+
181
+ // src/tools/decisions.ts
182
+ var import_zod4 = require("zod");
183
+ var DecisionsInputSchema = import_zod4.z.object({
184
+ limit: import_zod4.z.number().min(1).max(50).default(10).describe("Number of decisions to return"),
185
+ flagged: import_zod4.z.boolean().optional().describe("Filter to only flagged content (true) or safe content (false)"),
186
+ policyId: import_zod4.z.string().optional().describe("Filter by specific policy ID"),
187
+ contentType: import_zod4.z.enum(["text", "image", "video"]).optional().describe("Filter by content type")
188
+ });
189
+ function registerDecisionsTool(server, client) {
190
+ server.tool(
191
+ "get_recent_decisions",
192
+ "Get recent moderation decisions with optional filtering by outcome, content type, or policy.",
193
+ {
194
+ limit: DecisionsInputSchema.shape.limit,
195
+ flagged: DecisionsInputSchema.shape.flagged,
196
+ policyId: DecisionsInputSchema.shape.policyId,
197
+ contentType: DecisionsInputSchema.shape.contentType
198
+ },
199
+ async (args) => {
200
+ const input = DecisionsInputSchema.parse(args);
201
+ try {
202
+ const result = await client.listDecisions({ limit: input.limit });
203
+ let decisions = result.decisions || result;
204
+ if (input.flagged !== void 0) {
205
+ decisions = decisions.filter((d) => d.flagged === input.flagged);
206
+ }
207
+ if (input.policyId) {
208
+ decisions = decisions.filter((d) => d.policyId === input.policyId);
209
+ }
210
+ if (input.contentType) {
211
+ decisions = decisions.filter((d) => d.contentType === input.contentType);
212
+ }
213
+ return {
214
+ content: [
215
+ {
216
+ type: "text",
217
+ text: JSON.stringify({ decisions }, null, 2)
218
+ }
219
+ ]
220
+ };
221
+ } catch (error) {
222
+ return {
223
+ content: [
224
+ {
225
+ type: "text",
226
+ text: `Failed to get decisions: ${error instanceof Error ? error.message : "Unknown error"}`
227
+ }
228
+ ],
229
+ isError: true
230
+ };
231
+ }
232
+ }
233
+ );
234
+ }
235
+
236
+ // src/tools/index.ts
237
+ function registerTools(server, client) {
238
+ registerModerateTool(server, client);
239
+ registerValidateTool(server, client);
240
+ registerListPoliciesTool(server, client);
241
+ registerUsageStatsTool(server, client);
242
+ registerDecisionsTool(server, client);
243
+ }
244
+
245
+ // src/resources/policies.ts
246
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
247
+ function registerPolicyResources(server, client) {
248
+ server.resource(
249
+ "policies",
250
+ "vettly://policies",
251
+ {
252
+ description: "List of all available moderation policies",
253
+ mimeType: "application/json"
254
+ },
255
+ async (uri) => {
256
+ try {
257
+ const result = await client.listPolicies();
258
+ return {
259
+ contents: [
260
+ {
261
+ uri: uri.href,
262
+ mimeType: "application/json",
263
+ text: JSON.stringify(result, null, 2)
264
+ }
265
+ ]
266
+ };
267
+ } catch (error) {
268
+ return {
269
+ contents: [
270
+ {
271
+ uri: uri.href,
272
+ mimeType: "text/plain",
273
+ text: `Failed to list policies: ${error instanceof Error ? error.message : "Unknown error"}`
274
+ }
275
+ ]
276
+ };
277
+ }
278
+ }
279
+ );
280
+ server.resource(
281
+ "policy",
282
+ new import_mcp.ResourceTemplate("vettly://policies/{policyId}", { list: void 0 }),
283
+ {
284
+ description: "A specific moderation policy with its YAML configuration",
285
+ mimeType: "application/x-yaml"
286
+ },
287
+ async (uri, { policyId }) => {
288
+ try {
289
+ const result = await client.getPolicy(policyId);
290
+ return {
291
+ contents: [
292
+ {
293
+ uri: uri.href,
294
+ mimeType: "application/x-yaml",
295
+ text: result.yamlContent || result.yaml || JSON.stringify(result, null, 2)
296
+ }
297
+ ]
298
+ };
299
+ } catch (error) {
300
+ return {
301
+ contents: [
302
+ {
303
+ uri: uri.href,
304
+ mimeType: "text/plain",
305
+ text: `Failed to get policy: ${error instanceof Error ? error.message : "Unknown error"}`
306
+ }
307
+ ]
308
+ };
309
+ }
310
+ }
311
+ );
312
+ }
313
+
314
+ // src/resources/index.ts
315
+ function registerResources(server, client) {
316
+ registerPolicyResources(server, client);
317
+ }
318
+
319
+ // src/server.ts
320
+ function createVettlyMcpServer(config) {
321
+ const client = new import_sdk.ModerationClient({
322
+ apiKey: config.apiKey,
323
+ apiUrl: config.apiUrl
324
+ });
325
+ const server = new import_mcp2.McpServer({
326
+ name: "vettly",
327
+ version: "0.1.0"
328
+ });
329
+ registerTools(server, client);
330
+ registerResources(server, client);
331
+ return server;
332
+ }
333
+
334
+ // src/bin/vettly-mcp.ts
335
+ async function main() {
336
+ const apiKey = process.env.VETTLY_API_KEY;
337
+ if (!apiKey) {
338
+ console.error("Error: VETTLY_API_KEY environment variable is required");
339
+ console.error("");
340
+ console.error("Usage:");
341
+ console.error(" VETTLY_API_KEY=vettly_live_xxx npx @vettly/mcp");
342
+ console.error("");
343
+ console.error("Or configure in Claude Desktop:");
344
+ console.error(" {");
345
+ console.error(' "mcpServers": {');
346
+ console.error(' "vettly": {');
347
+ console.error(' "command": "npx",');
348
+ console.error(' "args": ["-y", "@vettly/mcp"],');
349
+ console.error(' "env": { "VETTLY_API_KEY": "vettly_live_xxx" }');
350
+ console.error(" }");
351
+ console.error(" }");
352
+ console.error(" }");
353
+ process.exit(1);
354
+ }
355
+ const server = createVettlyMcpServer({
356
+ apiKey,
357
+ apiUrl: process.env.VETTLY_API_URL
358
+ });
359
+ const transport = new import_stdio.StdioServerTransport();
360
+ await server.connect(transport);
361
+ }
362
+ main().catch((error) => {
363
+ console.error("Fatal error:", error);
364
+ process.exit(1);
365
+ });
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createVettlyMcpServer
4
+ } from "../chunk-VZJIPBOX.js";
5
+
6
+ // src/bin/vettly-mcp.ts
7
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
+ async function main() {
9
+ const apiKey = process.env.VETTLY_API_KEY;
10
+ if (!apiKey) {
11
+ console.error("Error: VETTLY_API_KEY environment variable is required");
12
+ console.error("");
13
+ console.error("Usage:");
14
+ console.error(" VETTLY_API_KEY=vettly_live_xxx npx @vettly/mcp");
15
+ console.error("");
16
+ console.error("Or configure in Claude Desktop:");
17
+ console.error(" {");
18
+ console.error(' "mcpServers": {');
19
+ console.error(' "vettly": {');
20
+ console.error(' "command": "npx",');
21
+ console.error(' "args": ["-y", "@vettly/mcp"],');
22
+ console.error(' "env": { "VETTLY_API_KEY": "vettly_live_xxx" }');
23
+ console.error(" }");
24
+ console.error(" }");
25
+ console.error(" }");
26
+ process.exit(1);
27
+ }
28
+ const server = createVettlyMcpServer({
29
+ apiKey,
30
+ apiUrl: process.env.VETTLY_API_URL
31
+ });
32
+ const transport = new StdioServerTransport();
33
+ await server.connect(transport);
34
+ }
35
+ main().catch((error) => {
36
+ console.error("Fatal error:", error);
37
+ process.exit(1);
38
+ });
@@ -0,0 +1,330 @@
1
+ // src/server.ts
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { ModerationClient } from "@vettly/sdk";
4
+
5
+ // src/tools/moderate.ts
6
+ import { z } from "zod";
7
+ var ModerateInputSchema = z.object({
8
+ content: z.string().min(1).max(1e5).describe("The content to moderate (text content, or URL for images/video)"),
9
+ policyId: z.string().min(1).describe("The policy ID to use for moderation"),
10
+ contentType: z.enum(["text", "image", "video"]).default("text").describe("Type of content being moderated")
11
+ });
12
+ function registerModerateTool(server, client) {
13
+ server.tool(
14
+ "moderate_content",
15
+ "Check text, image, or video content against a Vettly moderation policy. Returns safety assessment with category scores.",
16
+ {
17
+ content: ModerateInputSchema.shape.content,
18
+ policyId: ModerateInputSchema.shape.policyId,
19
+ contentType: ModerateInputSchema.shape.contentType
20
+ },
21
+ async (args) => {
22
+ const input = ModerateInputSchema.parse(args);
23
+ try {
24
+ const result = await client.check({
25
+ content: input.content,
26
+ policyId: input.policyId,
27
+ contentType: input.contentType
28
+ });
29
+ return {
30
+ content: [
31
+ {
32
+ type: "text",
33
+ text: JSON.stringify(
34
+ {
35
+ decisionId: result.decisionId,
36
+ safe: result.safe,
37
+ flagged: result.flagged,
38
+ action: result.action,
39
+ categories: result.categories,
40
+ provider: result.provider,
41
+ latency: result.latency,
42
+ cost: result.cost
43
+ },
44
+ null,
45
+ 2
46
+ )
47
+ }
48
+ ]
49
+ };
50
+ } catch (error) {
51
+ return {
52
+ content: [
53
+ {
54
+ type: "text",
55
+ text: `Moderation failed: ${error instanceof Error ? error.message : "Unknown error"}`
56
+ }
57
+ ],
58
+ isError: true
59
+ };
60
+ }
61
+ }
62
+ );
63
+ }
64
+
65
+ // src/tools/validate.ts
66
+ import { z as z2 } from "zod";
67
+ var ValidateInputSchema = z2.object({
68
+ yamlContent: z2.string().min(1).describe("The YAML policy content to validate")
69
+ });
70
+ function registerValidateTool(server, client) {
71
+ server.tool(
72
+ "validate_policy",
73
+ "Validate a Vettly policy YAML without saving it. Returns validation results with any syntax or configuration errors.",
74
+ {
75
+ yamlContent: ValidateInputSchema.shape.yamlContent
76
+ },
77
+ async (args) => {
78
+ const input = ValidateInputSchema.parse(args);
79
+ try {
80
+ const result = await client.validatePolicy(input.yamlContent);
81
+ return {
82
+ content: [
83
+ {
84
+ type: "text",
85
+ text: JSON.stringify(result, null, 2)
86
+ }
87
+ ]
88
+ };
89
+ } catch (error) {
90
+ return {
91
+ content: [
92
+ {
93
+ type: "text",
94
+ text: `Validation failed: ${error instanceof Error ? error.message : "Unknown error"}`
95
+ }
96
+ ],
97
+ isError: true
98
+ };
99
+ }
100
+ }
101
+ );
102
+ }
103
+
104
+ // src/tools/list-policies.ts
105
+ function registerListPoliciesTool(server, client) {
106
+ server.tool(
107
+ "list_policies",
108
+ "List all moderation policies available in your Vettly account.",
109
+ {},
110
+ async () => {
111
+ try {
112
+ const result = await client.listPolicies();
113
+ return {
114
+ content: [
115
+ {
116
+ type: "text",
117
+ text: JSON.stringify(result, null, 2)
118
+ }
119
+ ]
120
+ };
121
+ } catch (error) {
122
+ return {
123
+ content: [
124
+ {
125
+ type: "text",
126
+ text: `Failed to list policies: ${error instanceof Error ? error.message : "Unknown error"}`
127
+ }
128
+ ],
129
+ isError: true
130
+ };
131
+ }
132
+ }
133
+ );
134
+ }
135
+
136
+ // src/tools/usage-stats.ts
137
+ import { z as z3 } from "zod";
138
+ var UsageStatsInputSchema = z3.object({
139
+ days: z3.number().min(1).max(365).default(30).describe("Number of days to include in statistics")
140
+ });
141
+ function registerUsageStatsTool(server, client) {
142
+ server.tool(
143
+ "get_usage_stats",
144
+ "Get usage statistics for your Vettly account including request counts, costs, and moderation outcomes.",
145
+ {
146
+ days: UsageStatsInputSchema.shape.days
147
+ },
148
+ async (args) => {
149
+ const input = UsageStatsInputSchema.parse(args);
150
+ try {
151
+ const result = await client.getUsageStats(input.days);
152
+ return {
153
+ content: [
154
+ {
155
+ type: "text",
156
+ text: JSON.stringify(result, null, 2)
157
+ }
158
+ ]
159
+ };
160
+ } catch (error) {
161
+ return {
162
+ content: [
163
+ {
164
+ type: "text",
165
+ text: `Failed to get usage stats: ${error instanceof Error ? error.message : "Unknown error"}`
166
+ }
167
+ ],
168
+ isError: true
169
+ };
170
+ }
171
+ }
172
+ );
173
+ }
174
+
175
+ // src/tools/decisions.ts
176
+ import { z as z4 } from "zod";
177
+ var DecisionsInputSchema = z4.object({
178
+ limit: z4.number().min(1).max(50).default(10).describe("Number of decisions to return"),
179
+ flagged: z4.boolean().optional().describe("Filter to only flagged content (true) or safe content (false)"),
180
+ policyId: z4.string().optional().describe("Filter by specific policy ID"),
181
+ contentType: z4.enum(["text", "image", "video"]).optional().describe("Filter by content type")
182
+ });
183
+ function registerDecisionsTool(server, client) {
184
+ server.tool(
185
+ "get_recent_decisions",
186
+ "Get recent moderation decisions with optional filtering by outcome, content type, or policy.",
187
+ {
188
+ limit: DecisionsInputSchema.shape.limit,
189
+ flagged: DecisionsInputSchema.shape.flagged,
190
+ policyId: DecisionsInputSchema.shape.policyId,
191
+ contentType: DecisionsInputSchema.shape.contentType
192
+ },
193
+ async (args) => {
194
+ const input = DecisionsInputSchema.parse(args);
195
+ try {
196
+ const result = await client.listDecisions({ limit: input.limit });
197
+ let decisions = result.decisions || result;
198
+ if (input.flagged !== void 0) {
199
+ decisions = decisions.filter((d) => d.flagged === input.flagged);
200
+ }
201
+ if (input.policyId) {
202
+ decisions = decisions.filter((d) => d.policyId === input.policyId);
203
+ }
204
+ if (input.contentType) {
205
+ decisions = decisions.filter((d) => d.contentType === input.contentType);
206
+ }
207
+ return {
208
+ content: [
209
+ {
210
+ type: "text",
211
+ text: JSON.stringify({ decisions }, null, 2)
212
+ }
213
+ ]
214
+ };
215
+ } catch (error) {
216
+ return {
217
+ content: [
218
+ {
219
+ type: "text",
220
+ text: `Failed to get decisions: ${error instanceof Error ? error.message : "Unknown error"}`
221
+ }
222
+ ],
223
+ isError: true
224
+ };
225
+ }
226
+ }
227
+ );
228
+ }
229
+
230
+ // src/tools/index.ts
231
+ function registerTools(server, client) {
232
+ registerModerateTool(server, client);
233
+ registerValidateTool(server, client);
234
+ registerListPoliciesTool(server, client);
235
+ registerUsageStatsTool(server, client);
236
+ registerDecisionsTool(server, client);
237
+ }
238
+
239
+ // src/resources/policies.ts
240
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
241
+ function registerPolicyResources(server, client) {
242
+ server.resource(
243
+ "policies",
244
+ "vettly://policies",
245
+ {
246
+ description: "List of all available moderation policies",
247
+ mimeType: "application/json"
248
+ },
249
+ async (uri) => {
250
+ try {
251
+ const result = await client.listPolicies();
252
+ return {
253
+ contents: [
254
+ {
255
+ uri: uri.href,
256
+ mimeType: "application/json",
257
+ text: JSON.stringify(result, null, 2)
258
+ }
259
+ ]
260
+ };
261
+ } catch (error) {
262
+ return {
263
+ contents: [
264
+ {
265
+ uri: uri.href,
266
+ mimeType: "text/plain",
267
+ text: `Failed to list policies: ${error instanceof Error ? error.message : "Unknown error"}`
268
+ }
269
+ ]
270
+ };
271
+ }
272
+ }
273
+ );
274
+ server.resource(
275
+ "policy",
276
+ new ResourceTemplate("vettly://policies/{policyId}", { list: void 0 }),
277
+ {
278
+ description: "A specific moderation policy with its YAML configuration",
279
+ mimeType: "application/x-yaml"
280
+ },
281
+ async (uri, { policyId }) => {
282
+ try {
283
+ const result = await client.getPolicy(policyId);
284
+ return {
285
+ contents: [
286
+ {
287
+ uri: uri.href,
288
+ mimeType: "application/x-yaml",
289
+ text: result.yamlContent || result.yaml || JSON.stringify(result, null, 2)
290
+ }
291
+ ]
292
+ };
293
+ } catch (error) {
294
+ return {
295
+ contents: [
296
+ {
297
+ uri: uri.href,
298
+ mimeType: "text/plain",
299
+ text: `Failed to get policy: ${error instanceof Error ? error.message : "Unknown error"}`
300
+ }
301
+ ]
302
+ };
303
+ }
304
+ }
305
+ );
306
+ }
307
+
308
+ // src/resources/index.ts
309
+ function registerResources(server, client) {
310
+ registerPolicyResources(server, client);
311
+ }
312
+
313
+ // src/server.ts
314
+ function createVettlyMcpServer(config) {
315
+ const client = new ModerationClient({
316
+ apiKey: config.apiKey,
317
+ apiUrl: config.apiUrl
318
+ });
319
+ const server = new McpServer({
320
+ name: "vettly",
321
+ version: "0.1.0"
322
+ });
323
+ registerTools(server, client);
324
+ registerResources(server, client);
325
+ return server;
326
+ }
327
+
328
+ export {
329
+ createVettlyMcpServer
330
+ };
package/dist/index.cjs ADDED
@@ -0,0 +1,356 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createVettlyMcpServer: () => createVettlyMcpServer
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/server.ts
28
+ var import_mcp2 = require("@modelcontextprotocol/sdk/server/mcp.js");
29
+ var import_sdk = require("@vettly/sdk");
30
+
31
+ // src/tools/moderate.ts
32
+ var import_zod = require("zod");
33
+ var ModerateInputSchema = import_zod.z.object({
34
+ content: import_zod.z.string().min(1).max(1e5).describe("The content to moderate (text content, or URL for images/video)"),
35
+ policyId: import_zod.z.string().min(1).describe("The policy ID to use for moderation"),
36
+ contentType: import_zod.z.enum(["text", "image", "video"]).default("text").describe("Type of content being moderated")
37
+ });
38
+ function registerModerateTool(server, client) {
39
+ server.tool(
40
+ "moderate_content",
41
+ "Check text, image, or video content against a Vettly moderation policy. Returns safety assessment with category scores.",
42
+ {
43
+ content: ModerateInputSchema.shape.content,
44
+ policyId: ModerateInputSchema.shape.policyId,
45
+ contentType: ModerateInputSchema.shape.contentType
46
+ },
47
+ async (args) => {
48
+ const input = ModerateInputSchema.parse(args);
49
+ try {
50
+ const result = await client.check({
51
+ content: input.content,
52
+ policyId: input.policyId,
53
+ contentType: input.contentType
54
+ });
55
+ return {
56
+ content: [
57
+ {
58
+ type: "text",
59
+ text: JSON.stringify(
60
+ {
61
+ decisionId: result.decisionId,
62
+ safe: result.safe,
63
+ flagged: result.flagged,
64
+ action: result.action,
65
+ categories: result.categories,
66
+ provider: result.provider,
67
+ latency: result.latency,
68
+ cost: result.cost
69
+ },
70
+ null,
71
+ 2
72
+ )
73
+ }
74
+ ]
75
+ };
76
+ } catch (error) {
77
+ return {
78
+ content: [
79
+ {
80
+ type: "text",
81
+ text: `Moderation failed: ${error instanceof Error ? error.message : "Unknown error"}`
82
+ }
83
+ ],
84
+ isError: true
85
+ };
86
+ }
87
+ }
88
+ );
89
+ }
90
+
91
+ // src/tools/validate.ts
92
+ var import_zod2 = require("zod");
93
+ var ValidateInputSchema = import_zod2.z.object({
94
+ yamlContent: import_zod2.z.string().min(1).describe("The YAML policy content to validate")
95
+ });
96
+ function registerValidateTool(server, client) {
97
+ server.tool(
98
+ "validate_policy",
99
+ "Validate a Vettly policy YAML without saving it. Returns validation results with any syntax or configuration errors.",
100
+ {
101
+ yamlContent: ValidateInputSchema.shape.yamlContent
102
+ },
103
+ async (args) => {
104
+ const input = ValidateInputSchema.parse(args);
105
+ try {
106
+ const result = await client.validatePolicy(input.yamlContent);
107
+ return {
108
+ content: [
109
+ {
110
+ type: "text",
111
+ text: JSON.stringify(result, null, 2)
112
+ }
113
+ ]
114
+ };
115
+ } catch (error) {
116
+ return {
117
+ content: [
118
+ {
119
+ type: "text",
120
+ text: `Validation failed: ${error instanceof Error ? error.message : "Unknown error"}`
121
+ }
122
+ ],
123
+ isError: true
124
+ };
125
+ }
126
+ }
127
+ );
128
+ }
129
+
130
+ // src/tools/list-policies.ts
131
+ function registerListPoliciesTool(server, client) {
132
+ server.tool(
133
+ "list_policies",
134
+ "List all moderation policies available in your Vettly account.",
135
+ {},
136
+ async () => {
137
+ try {
138
+ const result = await client.listPolicies();
139
+ return {
140
+ content: [
141
+ {
142
+ type: "text",
143
+ text: JSON.stringify(result, null, 2)
144
+ }
145
+ ]
146
+ };
147
+ } catch (error) {
148
+ return {
149
+ content: [
150
+ {
151
+ type: "text",
152
+ text: `Failed to list policies: ${error instanceof Error ? error.message : "Unknown error"}`
153
+ }
154
+ ],
155
+ isError: true
156
+ };
157
+ }
158
+ }
159
+ );
160
+ }
161
+
162
+ // src/tools/usage-stats.ts
163
+ var import_zod3 = require("zod");
164
+ var UsageStatsInputSchema = import_zod3.z.object({
165
+ days: import_zod3.z.number().min(1).max(365).default(30).describe("Number of days to include in statistics")
166
+ });
167
+ function registerUsageStatsTool(server, client) {
168
+ server.tool(
169
+ "get_usage_stats",
170
+ "Get usage statistics for your Vettly account including request counts, costs, and moderation outcomes.",
171
+ {
172
+ days: UsageStatsInputSchema.shape.days
173
+ },
174
+ async (args) => {
175
+ const input = UsageStatsInputSchema.parse(args);
176
+ try {
177
+ const result = await client.getUsageStats(input.days);
178
+ return {
179
+ content: [
180
+ {
181
+ type: "text",
182
+ text: JSON.stringify(result, null, 2)
183
+ }
184
+ ]
185
+ };
186
+ } catch (error) {
187
+ return {
188
+ content: [
189
+ {
190
+ type: "text",
191
+ text: `Failed to get usage stats: ${error instanceof Error ? error.message : "Unknown error"}`
192
+ }
193
+ ],
194
+ isError: true
195
+ };
196
+ }
197
+ }
198
+ );
199
+ }
200
+
201
+ // src/tools/decisions.ts
202
+ var import_zod4 = require("zod");
203
+ var DecisionsInputSchema = import_zod4.z.object({
204
+ limit: import_zod4.z.number().min(1).max(50).default(10).describe("Number of decisions to return"),
205
+ flagged: import_zod4.z.boolean().optional().describe("Filter to only flagged content (true) or safe content (false)"),
206
+ policyId: import_zod4.z.string().optional().describe("Filter by specific policy ID"),
207
+ contentType: import_zod4.z.enum(["text", "image", "video"]).optional().describe("Filter by content type")
208
+ });
209
+ function registerDecisionsTool(server, client) {
210
+ server.tool(
211
+ "get_recent_decisions",
212
+ "Get recent moderation decisions with optional filtering by outcome, content type, or policy.",
213
+ {
214
+ limit: DecisionsInputSchema.shape.limit,
215
+ flagged: DecisionsInputSchema.shape.flagged,
216
+ policyId: DecisionsInputSchema.shape.policyId,
217
+ contentType: DecisionsInputSchema.shape.contentType
218
+ },
219
+ async (args) => {
220
+ const input = DecisionsInputSchema.parse(args);
221
+ try {
222
+ const result = await client.listDecisions({ limit: input.limit });
223
+ let decisions = result.decisions || result;
224
+ if (input.flagged !== void 0) {
225
+ decisions = decisions.filter((d) => d.flagged === input.flagged);
226
+ }
227
+ if (input.policyId) {
228
+ decisions = decisions.filter((d) => d.policyId === input.policyId);
229
+ }
230
+ if (input.contentType) {
231
+ decisions = decisions.filter((d) => d.contentType === input.contentType);
232
+ }
233
+ return {
234
+ content: [
235
+ {
236
+ type: "text",
237
+ text: JSON.stringify({ decisions }, null, 2)
238
+ }
239
+ ]
240
+ };
241
+ } catch (error) {
242
+ return {
243
+ content: [
244
+ {
245
+ type: "text",
246
+ text: `Failed to get decisions: ${error instanceof Error ? error.message : "Unknown error"}`
247
+ }
248
+ ],
249
+ isError: true
250
+ };
251
+ }
252
+ }
253
+ );
254
+ }
255
+
256
+ // src/tools/index.ts
257
+ function registerTools(server, client) {
258
+ registerModerateTool(server, client);
259
+ registerValidateTool(server, client);
260
+ registerListPoliciesTool(server, client);
261
+ registerUsageStatsTool(server, client);
262
+ registerDecisionsTool(server, client);
263
+ }
264
+
265
+ // src/resources/policies.ts
266
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
267
+ function registerPolicyResources(server, client) {
268
+ server.resource(
269
+ "policies",
270
+ "vettly://policies",
271
+ {
272
+ description: "List of all available moderation policies",
273
+ mimeType: "application/json"
274
+ },
275
+ async (uri) => {
276
+ try {
277
+ const result = await client.listPolicies();
278
+ return {
279
+ contents: [
280
+ {
281
+ uri: uri.href,
282
+ mimeType: "application/json",
283
+ text: JSON.stringify(result, null, 2)
284
+ }
285
+ ]
286
+ };
287
+ } catch (error) {
288
+ return {
289
+ contents: [
290
+ {
291
+ uri: uri.href,
292
+ mimeType: "text/plain",
293
+ text: `Failed to list policies: ${error instanceof Error ? error.message : "Unknown error"}`
294
+ }
295
+ ]
296
+ };
297
+ }
298
+ }
299
+ );
300
+ server.resource(
301
+ "policy",
302
+ new import_mcp.ResourceTemplate("vettly://policies/{policyId}", { list: void 0 }),
303
+ {
304
+ description: "A specific moderation policy with its YAML configuration",
305
+ mimeType: "application/x-yaml"
306
+ },
307
+ async (uri, { policyId }) => {
308
+ try {
309
+ const result = await client.getPolicy(policyId);
310
+ return {
311
+ contents: [
312
+ {
313
+ uri: uri.href,
314
+ mimeType: "application/x-yaml",
315
+ text: result.yamlContent || result.yaml || JSON.stringify(result, null, 2)
316
+ }
317
+ ]
318
+ };
319
+ } catch (error) {
320
+ return {
321
+ contents: [
322
+ {
323
+ uri: uri.href,
324
+ mimeType: "text/plain",
325
+ text: `Failed to get policy: ${error instanceof Error ? error.message : "Unknown error"}`
326
+ }
327
+ ]
328
+ };
329
+ }
330
+ }
331
+ );
332
+ }
333
+
334
+ // src/resources/index.ts
335
+ function registerResources(server, client) {
336
+ registerPolicyResources(server, client);
337
+ }
338
+
339
+ // src/server.ts
340
+ function createVettlyMcpServer(config) {
341
+ const client = new import_sdk.ModerationClient({
342
+ apiKey: config.apiKey,
343
+ apiUrl: config.apiUrl
344
+ });
345
+ const server = new import_mcp2.McpServer({
346
+ name: "vettly",
347
+ version: "0.1.0"
348
+ });
349
+ registerTools(server, client);
350
+ registerResources(server, client);
351
+ return server;
352
+ }
353
+ // Annotate the CommonJS export names for ESM import in node:
354
+ 0 && (module.exports = {
355
+ createVettlyMcpServer
356
+ });
@@ -0,0 +1,9 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+
3
+ interface VettlyMcpConfig {
4
+ apiKey: string;
5
+ apiUrl?: string;
6
+ }
7
+ declare function createVettlyMcpServer(config: VettlyMcpConfig): McpServer;
8
+
9
+ export { type VettlyMcpConfig, createVettlyMcpServer };
@@ -0,0 +1,9 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+
3
+ interface VettlyMcpConfig {
4
+ apiKey: string;
5
+ apiUrl?: string;
6
+ }
7
+ declare function createVettlyMcpServer(config: VettlyMcpConfig): McpServer;
8
+
9
+ export { type VettlyMcpConfig, createVettlyMcpServer };
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ import {
2
+ createVettlyMcpServer
3
+ } from "./chunk-VZJIPBOX.js";
4
+ export {
5
+ createVettlyMcpServer
6
+ };
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@vettly/mcp",
3
+ "version": "0.1.1",
4
+ "mcpName": "io.github.code-with-brian/vettly",
5
+ "description": "MCP server for Vettly content moderation API",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "bin": {
10
+ "vettly-mcp": "./dist/bin/vettly-mcp.js"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "require": "./dist/index.cjs"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup src/index.ts src/bin/vettly-mcp.ts --format esm,cjs --dts --clean",
25
+ "dev": "tsup src/index.ts src/bin/vettly-mcp.ts --format esm --watch",
26
+ "test": "bun test",
27
+ "prepublishOnly": "bun run build"
28
+ },
29
+ "keywords": [
30
+ "mcp",
31
+ "mcp-server",
32
+ "model-context-protocol",
33
+ "vettly",
34
+ "content-moderation",
35
+ "moderation",
36
+ "content-safety",
37
+ "trust-safety",
38
+ "ai",
39
+ "claude",
40
+ "cursor",
41
+ "anthropic",
42
+ "ugc-moderation",
43
+ "text-moderation",
44
+ "image-moderation",
45
+ "video-moderation",
46
+ "hate-speech-detection",
47
+ "nsfw-detection",
48
+ "csam-detection",
49
+ "spam-detection"
50
+ ],
51
+ "author": "Vettly",
52
+ "license": "MIT",
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "https://github.com/brian-nextaura/vettly-docs.git",
56
+ "directory": "packages/mcp"
57
+ },
58
+ "homepage": "https://vettly.dev",
59
+ "publishConfig": {
60
+ "access": "public",
61
+ "name": "@nextauralabs/vettly-mcp"
62
+ },
63
+ "bugs": {
64
+ "url": "https://github.com/brian-nextaura/vettly-docs/issues"
65
+ },
66
+ "dependencies": {
67
+ "@modelcontextprotocol/sdk": "^1.12.0",
68
+ "@vettly/sdk": "workspace:*",
69
+ "zod": "^3.22.4"
70
+ },
71
+ "devDependencies": {
72
+ "@types/node": "^20",
73
+ "tsup": "^8.5.1",
74
+ "typescript": "^5.9.3"
75
+ }
76
+ }