@tuteliq/mcp 2.2.0 → 3.0.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.
Files changed (38) hide show
  1. package/README.md +25 -4
  2. package/dist/server.d.ts +2 -0
  3. package/dist/server.d.ts.map +1 -0
  4. package/dist/server.js +29 -0
  5. package/dist/src/formatters.d.ts +8 -0
  6. package/dist/src/formatters.d.ts.map +1 -0
  7. package/dist/src/formatters.js +104 -0
  8. package/dist/src/index.d.ts +4 -0
  9. package/dist/src/index.d.ts.map +1 -0
  10. package/dist/src/index.js +39 -0
  11. package/dist/src/tools/admin.d.ts +4 -0
  12. package/dist/src/tools/admin.d.ts.map +1 -0
  13. package/dist/src/tools/admin.js +206 -0
  14. package/dist/src/tools/analysis.d.ts +4 -0
  15. package/dist/src/tools/analysis.d.ts.map +1 -0
  16. package/dist/src/tools/analysis.js +179 -0
  17. package/dist/src/tools/detection.d.ts +4 -0
  18. package/dist/src/tools/detection.d.ts.map +1 -0
  19. package/dist/src/tools/detection.js +170 -0
  20. package/dist/src/tools/fraud.d.ts +4 -0
  21. package/dist/src/tools/fraud.d.ts.map +1 -0
  22. package/dist/src/tools/fraud.js +109 -0
  23. package/dist/src/tools/media.d.ts +4 -0
  24. package/dist/src/tools/media.d.ts.map +1 -0
  25. package/dist/src/tools/media.js +172 -0
  26. package/dist/src/transport.d.ts +7 -0
  27. package/dist/src/transport.d.ts.map +1 -0
  28. package/dist/src/transport.js +60 -0
  29. package/dist-ui/action-plan.html +159 -0
  30. package/dist-ui/detection-result.html +159 -0
  31. package/dist-ui/emotions-result.html +159 -0
  32. package/dist-ui/media-result.html +159 -0
  33. package/dist-ui/multi-result.html +159 -0
  34. package/dist-ui/report-result.html +159 -0
  35. package/package.json +31 -10
  36. package/dist/index.d.ts +0 -3
  37. package/dist/index.d.ts.map +0 -1
  38. package/dist/index.js +0 -1036
package/README.md CHANGED
@@ -14,8 +14,9 @@
14
14
  </p>
15
15
 
16
16
  <p align="center">
17
- <a href="https://api.tuteliq.ai/docs">API Docs</a> •
18
- <a href="https://tuteliq.app">Dashboard</a> •
17
+ <a href="https://docs.tuteliq.ai">API Docs</a> •
18
+ <a href="https://tuteliq.ai">Dashboard</a> •
19
+ <a href="https://trust.tuteliq.ai">Trust</a> •
19
20
  <a href="https://discord.gg/7kbTeRYRXD">Discord</a>
20
21
  </p>
21
22
 
@@ -202,7 +203,7 @@ The message contains direct exclusionary language...
202
203
 
203
204
  ## Get an API Key
204
205
 
205
- 1. Go to [tuteliq.app](https://tuteliq.app)
206
+ 1. Go to [tuteliq.ai](https://tuteliq.ai)
206
207
  2. Create an account
207
208
  3. Generate an API key
208
209
  4. Add it to your MCP config
@@ -232,7 +233,7 @@ Enable `PII_REDACTION_ENABLED=true` on your Tuteliq API to automatically strip e
232
233
 
233
234
  ## Support
234
235
 
235
- - **API Docs**: [api.tuteliq.ai/docs](https://api.tuteliq.ai/docs)
236
+ - **API Docs**: [docs.tuteliq.ai](https://docs.tuteliq.ai)
236
237
  - **Discord**: [discord.gg/7kbTeRYRXD](https://discord.gg/7kbTeRYRXD)
237
238
  - **Email**: support@tuteliq.ai
238
239
 
@@ -244,6 +245,26 @@ MIT License - see [LICENSE](LICENSE) for details.
244
245
 
245
246
  ---
246
247
 
248
+ ## Get Certified — Free
249
+
250
+ Tuteliq offers a **free certification program** for anyone who wants to deepen their understanding of online child safety. Complete a track, pass the quiz, and earn your official Tuteliq certificate — verified and shareable.
251
+
252
+ **Three tracks available:**
253
+
254
+ | Track | Who it's for | Duration |
255
+ |-------|-------------|----------|
256
+ | **Parents & Caregivers** | Parents, guardians, grandparents, teachers, coaches | ~90 min |
257
+ | **Young People (10–16)** | Young people who want to learn to spot manipulation | ~60 min |
258
+ | **Companies & Platforms** | Product managers, trust & safety teams, CTOs, compliance officers | ~120 min |
259
+
260
+ **Start here →** [tuteliq.ai/certify](https://tuteliq.ai/certify)
261
+
262
+ - 100% Free — no login required
263
+ - Verifiable certificate on completion
264
+ - Covers grooming recognition, sextortion, cyberbullying, regulatory obligations (KOSA, EU DSA), and more
265
+
266
+ ---
267
+
247
268
  ## The Mission: Why This Matters
248
269
 
249
270
  Before you decide to contribute or sponsor, read these numbers. They are not projections. They are not estimates from a pitch deck. They are verified statistics from the University of Edinburgh, UNICEF, NCMEC, and Interpol.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../server.ts"],"names":[],"mappings":""}
package/dist/server.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const express_1 = __importDefault(require("express"));
7
+ const cors_1 = __importDefault(require("cors"));
8
+ const index_js_1 = require("./src/index.js");
9
+ const transport_js_1 = require("./src/transport.js");
10
+ const app = (0, express_1.default)();
11
+ const port = parseInt(process.env.PORT || '3001', 10);
12
+ app.use((0, cors_1.default)());
13
+ app.use(express_1.default.json());
14
+ const mcpServer = (0, index_js_1.createServer)();
15
+ const mcpHandler = (0, transport_js_1.createHttpHandler)(mcpServer);
16
+ app.all('/mcp', (req, res) => {
17
+ mcpHandler(req, res).catch((err) => {
18
+ console.error('MCP handler error:', err);
19
+ if (!res.headersSent) {
20
+ res.status(500).json({ error: 'Internal server error' });
21
+ }
22
+ });
23
+ });
24
+ app.get('/health', (_req, res) => {
25
+ res.json({ status: 'ok', version: '3.0.0' });
26
+ });
27
+ app.listen(port, () => {
28
+ console.error(`Tuteliq MCP App server running on http://localhost:${port}/mcp`);
29
+ });
@@ -0,0 +1,8 @@
1
+ import type { DetectionResult, AnalyseMultiResult, VideoAnalysisResult } from '@tuteliq/sdk';
2
+ export declare const severityEmoji: Record<string, string>;
3
+ export declare const riskEmoji: Record<string, string>;
4
+ export declare const trendEmoji: Record<string, string>;
5
+ export declare function formatDetectionResult(result: DetectionResult): string;
6
+ export declare function formatMultiResult(result: AnalyseMultiResult): string;
7
+ export declare function formatVideoResult(result: VideoAnalysisResult): string;
8
+ //# sourceMappingURL=formatters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatters.d.ts","sourceRoot":"","sources":["../../src/formatters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAE7F,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKhD,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAO5C,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAI7C,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAuCrE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CA2BpE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAoBrE"}
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.trendEmoji = exports.riskEmoji = exports.severityEmoji = void 0;
4
+ exports.formatDetectionResult = formatDetectionResult;
5
+ exports.formatMultiResult = formatMultiResult;
6
+ exports.formatVideoResult = formatVideoResult;
7
+ exports.severityEmoji = {
8
+ low: '\u{1F7E1}',
9
+ medium: '\u{1F7E0}',
10
+ high: '\u{1F534}',
11
+ critical: '\u26D4',
12
+ };
13
+ exports.riskEmoji = {
14
+ safe: '\u2705',
15
+ none: '\u2705',
16
+ low: '\u{1F7E1}',
17
+ medium: '\u{1F7E0}',
18
+ high: '\u{1F534}',
19
+ critical: '\u26D4',
20
+ };
21
+ exports.trendEmoji = {
22
+ improving: '\u{1F4C8}',
23
+ stable: '\u27A1\uFE0F',
24
+ worsening: '\u{1F4C9}',
25
+ };
26
+ function formatDetectionResult(result) {
27
+ const detected = result.detected;
28
+ const levelEmoji = exports.riskEmoji[result.level] || '\u26AA';
29
+ const label = result.endpoint
30
+ .split('_')
31
+ .map(w => w.charAt(0).toUpperCase() + w.slice(1))
32
+ .join(' ');
33
+ const header = detected
34
+ ? `## ${levelEmoji} ${label} Detected`
35
+ : `## \u2705 No ${label} Detected`;
36
+ const categories = result.categories.length > 0
37
+ ? `**Categories:** ${result.categories.map(c => c.tag).join(', ')}`
38
+ : '';
39
+ const evidence = result.evidence && result.evidence.length > 0
40
+ ? `### Evidence\n${result.evidence.map(e => `- _"${e.text}"_ \u2014 **${e.tactic}** (weight: ${e.weight.toFixed(2)})`).join('\n')}`
41
+ : '';
42
+ const calibration = result.age_calibration?.applied
43
+ ? `**Age Calibration:** ${result.age_calibration.age_group} (${result.age_calibration.multiplier}x)`
44
+ : '';
45
+ return `${header}
46
+
47
+ **Risk Score:** ${(result.risk_score * 100).toFixed(0)}%
48
+ **Level:** ${result.level}
49
+ **Confidence:** ${(result.confidence * 100).toFixed(0)}%
50
+ ${categories}
51
+
52
+ ### Rationale
53
+ ${result.rationale}
54
+
55
+ ### Recommended Action
56
+ \`${result.recommended_action}\`
57
+
58
+ ${evidence}
59
+ ${calibration}`.trim();
60
+ }
61
+ function formatMultiResult(result) {
62
+ const s = result.summary;
63
+ const overallEmoji = exports.riskEmoji[s.overall_risk_level] || '\u26AA';
64
+ const summarySection = `## Multi-Endpoint Analysis
65
+
66
+ **Overall Risk:** ${overallEmoji} ${s.overall_risk_level}
67
+ **Endpoints Analyzed:** ${s.total_endpoints}
68
+ **Threats Detected:** ${s.detected_count}
69
+ **Highest Risk:** ${s.highest_risk.endpoint} (${(s.highest_risk.risk_score * 100).toFixed(0)}%)
70
+ ${result.cross_endpoint_modifier ? `**Cross-Endpoint Modifier:** ${result.cross_endpoint_modifier.toFixed(2)}x` : ''}`;
71
+ const perEndpoint = result.results
72
+ .map(r => {
73
+ const emoji = r.detected ? (exports.riskEmoji[r.level] || '\u26AA') : '\u2705';
74
+ return `### ${emoji} ${r.endpoint}
75
+ **Detected:** ${r.detected ? 'Yes' : 'No'} | **Risk:** ${(r.risk_score * 100).toFixed(0)}% | **Level:** ${r.level}
76
+ ${r.categories.length > 0 ? `**Categories:** ${r.categories.map(c => c.tag).join(', ')}` : ''}
77
+ ${r.rationale}`;
78
+ })
79
+ .join('\n\n');
80
+ return `${summarySection}
81
+
82
+ ---
83
+
84
+ ${perEndpoint}`;
85
+ }
86
+ function formatVideoResult(result) {
87
+ const emoji = exports.severityEmoji[result.overall_severity] || '\u2705';
88
+ const findingsSection = result.safety_findings.length > 0
89
+ ? result.safety_findings
90
+ .map(f => {
91
+ const fEmoji = exports.severityEmoji[f.severity <= 0.3 ? 'low' : f.severity <= 0.6 ? 'medium' : f.severity <= 0.85 ? 'high' : 'critical'] || '\u26AA';
92
+ return `- \`${f.timestamp.toFixed(1)}s\` (frame ${f.frame_index}) ${fEmoji} ${f.description}\n Categories: ${f.categories.join(', ')} | Severity: ${(f.severity * 100).toFixed(0)}%`;
93
+ })
94
+ .join('\n')
95
+ : '_No safety findings._';
96
+ return `## \u{1F3AC} Video Analysis
97
+
98
+ **Overall Severity:** ${emoji} ${result.overall_severity}
99
+ **Overall Risk Score:** ${(result.overall_risk_score * 100).toFixed(0)}%
100
+ **Frames Analyzed:** ${result.frames_analyzed}
101
+
102
+ ### Safety Findings
103
+ ${findingsSection}`;
104
+ }
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ export declare function createServer(): McpServer;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAUpE,wBAAgB,YAAY,IAAI,SAAS,CAsBxC"}
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.createServer = createServer;
5
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
6
+ const sdk_1 = require("@tuteliq/sdk");
7
+ const detection_js_1 = require("./tools/detection.js");
8
+ const fraud_js_1 = require("./tools/fraud.js");
9
+ const media_js_1 = require("./tools/media.js");
10
+ const analysis_js_1 = require("./tools/analysis.js");
11
+ const admin_js_1 = require("./tools/admin.js");
12
+ const transport_js_1 = require("./transport.js");
13
+ function createServer() {
14
+ const apiKey = process.env.TUTELIQ_API_KEY;
15
+ if (!apiKey) {
16
+ console.error('Error: TUTELIQ_API_KEY environment variable is required');
17
+ process.exit(1);
18
+ }
19
+ const client = new sdk_1.Tuteliq(apiKey);
20
+ const server = new mcp_js_1.McpServer({
21
+ name: 'tuteliq-mcp',
22
+ version: '3.0.0',
23
+ });
24
+ // Register all tool groups
25
+ (0, detection_js_1.registerDetectionTools)(server, client);
26
+ (0, fraud_js_1.registerFraudTools)(server, client);
27
+ (0, media_js_1.registerMediaTools)(server, client);
28
+ (0, analysis_js_1.registerAnalysisTools)(server, client);
29
+ (0, admin_js_1.registerAdminTools)(server, client);
30
+ return server;
31
+ }
32
+ // Direct execution: stdio mode
33
+ if ((0, transport_js_1.getTransportMode)() === 'stdio') {
34
+ const server = createServer();
35
+ (0, transport_js_1.startStdio)(server).catch((error) => {
36
+ console.error('Fatal error:', error);
37
+ process.exit(1);
38
+ });
39
+ }
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { Tuteliq } from '@tuteliq/sdk';
3
+ export declare function registerAdminTools(server: McpServer, client: Tuteliq): void;
4
+ //# sourceMappingURL=admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../../src/tools/admin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,OAAO,EAAsG,MAAM,cAAc,CAAC;AAGhJ,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAmU3E"}
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerAdminTools = registerAdminTools;
4
+ const zod_1 = require("zod");
5
+ const formatters_js_1 = require("../formatters.js");
6
+ function registerAdminTools(server, client) {
7
+ // =========================================================================
8
+ // Webhook Management
9
+ // =========================================================================
10
+ server.tool('list_webhooks', 'List all webhooks configured for your account.', {}, async () => {
11
+ const result = await client.listWebhooks();
12
+ if (result.webhooks.length === 0) {
13
+ return { content: [{ type: 'text', text: 'No webhooks configured.' }] };
14
+ }
15
+ const lines = result.webhooks.map(w => `- ${w.is_active ? '\u{1F7E2}' : '\u26AA'} **${w.name}** \u2014 \`${w.url}\`\n Events: ${w.events.join(', ')} _(${w.id})_`).join('\n');
16
+ return { content: [{ type: 'text', text: `## Webhooks\n\n${lines}` }] };
17
+ });
18
+ server.tool('create_webhook', 'Create a new webhook endpoint.', {
19
+ name: zod_1.z.string().describe('Display name for the webhook'),
20
+ url: zod_1.z.string().describe('HTTPS URL to receive webhook payloads'),
21
+ events: zod_1.z.array(zod_1.z.string()).describe('Event types to subscribe to'),
22
+ }, async ({ name, url, events }) => {
23
+ const result = await client.createWebhook({
24
+ name,
25
+ url,
26
+ events: events,
27
+ });
28
+ return { content: [{ type: 'text', text: `## \u2705 Webhook Created\n\n**ID:** ${result.id}\n**Name:** ${result.name}\n**URL:** ${result.url}\n**Events:** ${result.events.join(', ')}\n\n\u26A0\uFE0F **Secret (save this \u2014 shown only once):**\n\`${result.secret}\`` }] };
29
+ });
30
+ server.tool('update_webhook', 'Update an existing webhook configuration.', {
31
+ id: zod_1.z.string().describe('Webhook ID'),
32
+ name: zod_1.z.string().optional().describe('New display name'),
33
+ url: zod_1.z.string().optional().describe('New HTTPS URL'),
34
+ events: zod_1.z.array(zod_1.z.string()).optional().describe('New event subscriptions'),
35
+ is_active: zod_1.z.boolean().optional().describe('Enable or disable the webhook'),
36
+ }, async ({ id, name, url, events, is_active }) => {
37
+ const result = await client.updateWebhook(id, {
38
+ name,
39
+ url,
40
+ events: events,
41
+ isActive: is_active,
42
+ });
43
+ return { content: [{ type: 'text', text: `## \u2705 Webhook Updated\n\n**ID:** ${result.id}\n**Name:** ${result.name}\n**Active:** ${result.is_active ? '\u{1F7E2} Yes' : '\u26AA No'}` }] };
44
+ });
45
+ server.tool('delete_webhook', 'Permanently delete a webhook.', { id: zod_1.z.string().describe('Webhook ID to delete') }, async ({ id }) => {
46
+ await client.deleteWebhook(id);
47
+ return { content: [{ type: 'text', text: `## \u2705 Webhook Deleted\n\nWebhook \`${id}\` has been permanently deleted.` }] };
48
+ });
49
+ server.tool('test_webhook', 'Send a test payload to a webhook to verify it is working correctly.', { id: zod_1.z.string().describe('Webhook ID to test') }, async ({ id }) => {
50
+ const result = await client.testWebhook(id);
51
+ return { content: [{ type: 'text', text: `## ${result.success ? '\u2705' : '\u274C'} Webhook Test\n\n**Success:** ${result.success}\n**Status Code:** ${result.status_code}\n**Latency:** ${result.latency_ms}ms${result.error ? `\n**Error:** ${result.error}` : ''}` }] };
52
+ });
53
+ server.tool('regenerate_webhook_secret', 'Regenerate a webhook signing secret.', { id: zod_1.z.string().describe('Webhook ID') }, async ({ id }) => {
54
+ const result = await client.regenerateWebhookSecret(id);
55
+ return { content: [{ type: 'text', text: `## \u2705 Secret Regenerated\n\nThe old secret has been invalidated.\n\n\u26A0\uFE0F **New Secret (save this \u2014 shown only once):**\n\`${result.secret}\`` }] };
56
+ });
57
+ // =========================================================================
58
+ // Pricing
59
+ // =========================================================================
60
+ server.tool('get_pricing', 'Get available pricing plans for Tuteliq.', {}, async () => {
61
+ const result = await client.getPricing();
62
+ const lines = result.plans.map(p => `### ${p.name}\n**Price:** ${p.price}\n${p.features.map(f => `- ${f}`).join('\n')}`).join('\n\n');
63
+ return { content: [{ type: 'text', text: `## Tuteliq Pricing\n\n${lines}` }] };
64
+ });
65
+ server.tool('get_pricing_details', 'Get detailed pricing plans.', {}, async () => {
66
+ const result = await client.getPricingDetails();
67
+ const lines = result.plans.map(p => `### ${p.name}\n**Monthly:** ${p.price_monthly}/mo | **Yearly:** ${p.price_yearly}/mo\n**API Calls:** ${p.api_calls_per_month}/mo | **Rate Limit:** ${p.rate_limit}/min\n${p.features.map(f => `- ${f}`).join('\n')}`).join('\n\n');
68
+ return { content: [{ type: 'text', text: `## Tuteliq Pricing Details\n\n${lines}` }] };
69
+ });
70
+ // =========================================================================
71
+ // Usage & Billing
72
+ // =========================================================================
73
+ server.tool('get_usage_history', 'Get daily usage history for the past N days.', { days: zod_1.z.number().optional().describe('Number of days to retrieve (1-30, default: 7)') }, async ({ days }) => {
74
+ const result = await client.getUsageHistory(days);
75
+ if (result.days.length === 0) {
76
+ return { content: [{ type: 'text', text: 'No usage data available.' }] };
77
+ }
78
+ const lines = result.days.map(d => `| ${d.date} | ${d.total_requests} | ${d.success_requests} | ${d.error_requests} |`).join('\n');
79
+ return { content: [{ type: 'text', text: `## Usage History\n\n| Date | Total | Success | Errors |\n|------|-------|---------|--------|\n${lines}` }] };
80
+ });
81
+ server.tool('get_usage_by_tool', 'Get usage broken down by tool/endpoint for a specific date.', { date: zod_1.z.string().optional().describe('Date in YYYY-MM-DD format (default: today)') }, async ({ date }) => {
82
+ const result = await client.getUsageByTool(date);
83
+ const toolLines = Object.entries(result.tools).map(([tool, count]) => `- **${tool}:** ${count}`).join('\n');
84
+ const endpointLines = Object.entries(result.endpoints).map(([ep, count]) => `- **${ep}:** ${count}`).join('\n');
85
+ return { content: [{ type: 'text', text: `## Usage by Tool \u2014 ${result.date}\n\n### By Tool\n${toolLines || '_No data_'}\n\n### By Endpoint\n${endpointLines || '_No data_'}` }] };
86
+ });
87
+ server.tool('get_usage_monthly', 'Get monthly usage summary.', {}, async () => {
88
+ const result = await client.getUsageMonthly();
89
+ const text = `## Monthly Usage
90
+
91
+ **Tier:** ${result.tier_display_name}
92
+ **Billing Period:** ${result.billing.current_period_start} \u2192 ${result.billing.current_period_end} (${result.billing.days_remaining} days left)
93
+
94
+ ### Usage
95
+ **Used:** ${result.usage.used} / ${result.usage.limit} (${result.usage.percent_used.toFixed(1)}%)
96
+ **Remaining:** ${result.usage.remaining}
97
+ **Rate Limit:** ${result.rate_limit.requests_per_minute}/min
98
+
99
+ ${result.recommendations ? `### Recommendation\n${result.recommendations.reason}\n**Suggested Tier:** ${result.recommendations.suggested_tier}\n[Upgrade](${result.recommendations.upgrade_url})` : ''}`;
100
+ return { content: [{ type: 'text', text }] };
101
+ });
102
+ // =========================================================================
103
+ // GDPR Account
104
+ // =========================================================================
105
+ server.tool('delete_account_data', 'Delete all account data (GDPR Right to Erasure).', {}, async () => {
106
+ const result = await client.deleteAccountData();
107
+ return { content: [{ type: 'text', text: `## \u2705 Account Data Deleted\n\n**Message:** ${result.message}\n**Records Deleted:** ${result.deleted_count}` }] };
108
+ });
109
+ server.tool('export_account_data', 'Export all account data as JSON (GDPR Data Portability).', {}, async () => {
110
+ const result = await client.exportAccountData();
111
+ const collections = Object.keys(result.data).join(', ');
112
+ return { content: [{ type: 'text', text: `## \u{1F4E6} Account Data Export\n\n**User ID:** ${result.userId}\n**Exported At:** ${result.exportedAt}\n**Collections:** ${collections}\n\n\`\`\`json\n${JSON.stringify(result.data, null, 2).slice(0, 5000)}\n\`\`\`` }] };
113
+ });
114
+ const consentTypeEnum = zod_1.z.enum(['data_processing', 'analytics', 'marketing', 'third_party_sharing', 'child_safety_monitoring']);
115
+ server.tool('record_consent', 'Record user consent for data processing (GDPR Article 7).', {
116
+ consent_type: consentTypeEnum.describe('Type of consent to record'),
117
+ version: zod_1.z.string().describe('Policy version the user is consenting to'),
118
+ }, async ({ consent_type, version }) => {
119
+ const result = await client.recordConsent({ consent_type: consent_type, version });
120
+ return { content: [{ type: 'text', text: `## \u2705 Consent Recorded\n\n**Type:** ${result.consent.consent_type}\n**Status:** ${result.consent.status}\n**Version:** ${result.consent.version}` }] };
121
+ });
122
+ server.tool('get_consent_status', 'Get current consent status.', { type: consentTypeEnum.optional().describe('Optional: filter by consent type') }, async ({ type }) => {
123
+ const result = await client.getConsentStatus(type);
124
+ if (result.consents.length === 0) {
125
+ return { content: [{ type: 'text', text: 'No consent records found.' }] };
126
+ }
127
+ const lines = result.consents.map(c => `- **${c.consent_type}**: ${c.status === 'granted' ? '\u2705' : '\u274C'} ${c.status} (v${c.version})`).join('\n');
128
+ return { content: [{ type: 'text', text: `## Consent Status\n\n${lines}` }] };
129
+ });
130
+ server.tool('withdraw_consent', 'Withdraw a previously granted consent.', { type: consentTypeEnum.describe('Type of consent to withdraw') }, async ({ type }) => {
131
+ const result = await client.withdrawConsent(type);
132
+ return { content: [{ type: 'text', text: `## \u26A0\uFE0F Consent Withdrawn\n\n**Type:** ${result.consent.consent_type}\n**Status:** ${result.consent.status}` }] };
133
+ });
134
+ server.tool('rectify_data', 'Rectify (correct) user data (GDPR Right to Rectification).', {
135
+ collection: zod_1.z.string().describe('Firestore collection name'),
136
+ document_id: zod_1.z.string().describe('Document ID to rectify'),
137
+ fields: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).describe('Fields to update'),
138
+ }, async ({ collection, document_id, fields }) => {
139
+ const result = await client.rectifyData({ collection, document_id, fields: fields });
140
+ return { content: [{ type: 'text', text: `## \u2705 Data Rectified\n\n**Message:** ${result.message}\n**Updated Fields:** ${result.updated_fields.join(', ')}` }] };
141
+ });
142
+ server.tool('get_audit_logs', 'Get audit trail of data operations.', {
143
+ action: zod_1.z.enum(['data_access', 'data_export', 'data_deletion', 'data_rectification', 'consent_granted', 'consent_withdrawn', 'breach_notification']).optional().describe('Filter by action type'),
144
+ limit: zod_1.z.number().optional().describe('Maximum number of results'),
145
+ }, async ({ action, limit }) => {
146
+ const result = await client.getAuditLogs({ action: action, limit });
147
+ if (result.audit_logs.length === 0) {
148
+ return { content: [{ type: 'text', text: 'No audit logs found.' }] };
149
+ }
150
+ const logLines = result.audit_logs.map(l => `- \`${l.created_at}\` **${l.action}** _(${l.id})_`).join('\n');
151
+ return { content: [{ type: 'text', text: `## \u{1F4CB} Audit Logs\n\n${logLines}` }] };
152
+ });
153
+ // =========================================================================
154
+ // Breach Management
155
+ // =========================================================================
156
+ server.tool('log_breach', 'Log a new data breach (GDPR Article 33/34).', {
157
+ title: zod_1.z.string().describe('Brief title of the breach'),
158
+ description: zod_1.z.string().describe('Detailed description'),
159
+ severity: zod_1.z.enum(['low', 'medium', 'high', 'critical']).describe('Breach severity'),
160
+ affected_user_ids: zod_1.z.array(zod_1.z.string()).describe('List of affected user IDs'),
161
+ data_categories: zod_1.z.array(zod_1.z.string()).describe('Categories of data affected'),
162
+ reported_by: zod_1.z.string().describe('Who reported the breach'),
163
+ }, async ({ title, description, severity, affected_user_ids, data_categories, reported_by }) => {
164
+ const result = await client.logBreach({
165
+ title,
166
+ description,
167
+ severity: severity,
168
+ affected_user_ids,
169
+ data_categories,
170
+ reported_by,
171
+ });
172
+ const b = result.breach;
173
+ return { content: [{ type: 'text', text: `## \u26A0\uFE0F Breach Logged\n\n**ID:** ${b.id}\n**Title:** ${b.title}\n**Severity:** ${formatters_js_1.severityEmoji[b.severity] || '\u26AA'} ${b.severity}\n**Status:** ${b.status}\n**Notification Deadline:** ${b.notification_deadline}\n**Affected Users:** ${b.affected_user_ids.length}\n**Data Categories:** ${b.data_categories.join(', ')}` }] };
174
+ });
175
+ const breachStatusEnum = zod_1.z.enum(['detected', 'investigating', 'contained', 'reported', 'resolved']);
176
+ server.tool('list_breaches', 'List all data breaches.', {
177
+ status: breachStatusEnum.optional().describe('Filter by status'),
178
+ limit: zod_1.z.number().optional().describe('Maximum number of results'),
179
+ }, async ({ status, limit }) => {
180
+ const result = await client.listBreaches({ status: status, limit });
181
+ if (result.breaches.length === 0) {
182
+ return { content: [{ type: 'text', text: 'No breaches found.' }] };
183
+ }
184
+ const breachLines = result.breaches.map(b => `- ${formatters_js_1.severityEmoji[b.severity] || '\u26AA'} **${b.title}** \u2014 ${b.status} _(${b.id})_`).join('\n');
185
+ return { content: [{ type: 'text', text: `## Data Breaches\n\n${breachLines}` }] };
186
+ });
187
+ server.tool('get_breach', 'Get details of a specific data breach.', { id: zod_1.z.string().describe('Breach ID') }, async ({ id }) => {
188
+ const result = await client.getBreach(id);
189
+ const b = result.breach;
190
+ return { content: [{ type: 'text', text: `## Breach Details\n\n**ID:** ${b.id}\n**Title:** ${b.title}\n**Severity:** ${formatters_js_1.severityEmoji[b.severity] || '\u26AA'} ${b.severity}\n**Status:** ${b.status}\n**Notification:** ${b.notification_status}\n**Reported By:** ${b.reported_by}\n**Deadline:** ${b.notification_deadline}\n**Created:** ${b.created_at}\n**Updated:** ${b.updated_at}\n\n### Description\n${b.description}\n\n**Affected Users:** ${b.affected_user_ids.join(', ')}\n**Data Categories:** ${b.data_categories.join(', ')}` }] };
191
+ });
192
+ server.tool('update_breach_status', 'Update a breach status and notification progress.', {
193
+ id: zod_1.z.string().describe('Breach ID'),
194
+ status: breachStatusEnum.describe('New breach status'),
195
+ notification_status: zod_1.z.enum(['pending', 'users_notified', 'dpa_notified', 'completed']).optional().describe('Notification progress'),
196
+ notes: zod_1.z.string().optional().describe('Additional notes'),
197
+ }, async ({ id, status, notification_status, notes }) => {
198
+ const result = await client.updateBreachStatus(id, {
199
+ status: status,
200
+ notification_status: notification_status,
201
+ notes,
202
+ });
203
+ const b = result.breach;
204
+ return { content: [{ type: 'text', text: `## \u2705 Breach Updated\n\n**ID:** ${b.id}\n**Status:** ${b.status}\n**Notification:** ${b.notification_status}` }] };
205
+ });
206
+ }
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { Tuteliq } from '@tuteliq/sdk';
3
+ export declare function registerAnalysisTools(server: McpServer, client: Tuteliq): void;
4
+ //# sourceMappingURL=analysis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analysis.d.ts","sourceRoot":"","sources":["../../../src/tools/analysis.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,cAAc,CAAC;AAc1D,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CA0M9E"}