@tuteliq/mcp 3.2.0 → 3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -1,29 +1,49 @@
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)();
1
+ import express from 'express';
2
+ import cors from 'cors';
3
+ import { createServer } from './src/index.js';
4
+ import { createHttpHandlers } from './src/transport.js';
5
+ const app = express();
11
6
  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);
7
+ app.use(cors());
8
+ app.use(express.json());
9
+ const { streamableHandler, sseHandler, messagesHandler, closeAll } = createHttpHandlers(createServer);
10
+ // Streamable HTTP transport (protocol version 2025-11-25)
16
11
  app.all('/mcp', (req, res) => {
17
- mcpHandler(req, res).catch((err) => {
12
+ streamableHandler(req, res).catch((err) => {
18
13
  console.error('MCP handler error:', err);
19
14
  if (!res.headersSent) {
20
15
  res.status(500).json({ error: 'Internal server error' });
21
16
  }
22
17
  });
23
18
  });
19
+ // Legacy SSE transport (protocol version 2024-11-05)
20
+ app.get('/sse', (req, res) => {
21
+ sseHandler(req, res).catch((err) => {
22
+ console.error('SSE handler error:', err);
23
+ if (!res.headersSent) {
24
+ res.status(500).json({ error: 'Internal server error' });
25
+ }
26
+ });
27
+ });
28
+ app.post('/messages', (req, res) => {
29
+ messagesHandler(req, res).catch((err) => {
30
+ console.error('Messages handler error:', err);
31
+ if (!res.headersSent) {
32
+ res.status(500).json({ error: 'Internal server error' });
33
+ }
34
+ });
35
+ });
24
36
  app.get('/health', (_req, res) => {
25
- res.json({ status: 'ok', version: '3.0.0' });
37
+ res.json({ status: 'ok', version: '3.2.0' });
38
+ });
39
+ const server = app.listen(port, () => {
40
+ console.error(`Tuteliq MCP server running on http://localhost:${port}`);
41
+ console.error(` Streamable HTTP: POST/GET/DELETE /mcp`);
42
+ console.error(` Legacy SSE: GET /sse + POST /messages`);
26
43
  });
27
- app.listen(port, () => {
28
- console.error(`Tuteliq MCP App server running on http://localhost:${port}/mcp`);
44
+ process.on('SIGINT', async () => {
45
+ console.error('Shutting down...');
46
+ await closeAll();
47
+ server.close();
48
+ process.exit(0);
29
49
  });
@@ -1,16 +1,10 @@
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 = {
1
+ export const severityEmoji = {
8
2
  low: '\u{1F7E1}',
9
3
  medium: '\u{1F7E0}',
10
4
  high: '\u{1F534}',
11
5
  critical: '\u26D4',
12
6
  };
13
- exports.riskEmoji = {
7
+ export const riskEmoji = {
14
8
  safe: '\u2705',
15
9
  none: '\u2705',
16
10
  low: '\u{1F7E1}',
@@ -18,14 +12,14 @@ exports.riskEmoji = {
18
12
  high: '\u{1F534}',
19
13
  critical: '\u26D4',
20
14
  };
21
- exports.trendEmoji = {
15
+ export const trendEmoji = {
22
16
  improving: '\u{1F4C8}',
23
17
  stable: '\u27A1\uFE0F',
24
18
  worsening: '\u{1F4C9}',
25
19
  };
26
- function formatDetectionResult(result) {
20
+ export function formatDetectionResult(result) {
27
21
  const detected = result.detected;
28
- const levelEmoji = exports.riskEmoji[result.level] || '\u26AA';
22
+ const levelEmoji = riskEmoji[result.level] || '\u26AA';
29
23
  const label = result.endpoint
30
24
  .split('_')
31
25
  .map(w => w.charAt(0).toUpperCase() + w.slice(1))
@@ -62,9 +56,9 @@ ${evidence}
62
56
  ${messageAnalysis}
63
57
  ${calibration}`.trim();
64
58
  }
65
- function formatMultiResult(result) {
59
+ export function formatMultiResult(result) {
66
60
  const s = result.summary;
67
- const overallEmoji = exports.riskEmoji[s.overall_risk_level] || '\u26AA';
61
+ const overallEmoji = riskEmoji[s.overall_risk_level] || '\u26AA';
68
62
  const summarySection = `## Multi-Endpoint Analysis
69
63
 
70
64
  **Overall Risk:** ${overallEmoji} ${s.overall_risk_level}
@@ -74,7 +68,7 @@ function formatMultiResult(result) {
74
68
  ${result.cross_endpoint_modifier ? `**Cross-Endpoint Modifier:** ${result.cross_endpoint_modifier.toFixed(2)}x` : ''}`;
75
69
  const perEndpoint = result.results
76
70
  .map(r => {
77
- const emoji = r.detected ? (exports.riskEmoji[r.level] || '\u26AA') : '\u2705';
71
+ const emoji = r.detected ? (riskEmoji[r.level] || '\u26AA') : '\u2705';
78
72
  return `### ${emoji} ${r.endpoint}
79
73
  **Detected:** ${r.detected ? 'Yes' : 'No'} | **Risk:** ${(r.risk_score * 100).toFixed(0)}% | **Level:** ${r.level}
80
74
  ${r.categories.length > 0 ? `**Categories:** ${r.categories.map(c => c.tag).join(', ')}` : ''}
@@ -87,12 +81,12 @@ ${r.rationale}`;
87
81
 
88
82
  ${perEndpoint}`;
89
83
  }
90
- function formatVideoResult(result) {
91
- const emoji = exports.severityEmoji[result.overall_severity] || '\u2705';
84
+ export function formatVideoResult(result) {
85
+ const emoji = severityEmoji[result.overall_severity] || '\u2705';
92
86
  const findingsSection = result.safety_findings.length > 0
93
87
  ? result.safety_findings
94
88
  .map(f => {
95
- const fEmoji = exports.severityEmoji[f.severity <= 0.3 ? 'low' : f.severity <= 0.6 ? 'medium' : f.severity <= 0.85 ? 'high' : 'critical'] || '\u26AA';
89
+ const fEmoji = severityEmoji[f.severity <= 0.3 ? 'low' : f.severity <= 0.6 ? 'medium' : f.severity <= 0.85 ? 'high' : 'critical'] || '\u26AA';
96
90
  return `- \`${f.timestamp.toFixed(1)}s\` (frame ${f.frame_index}) ${fEmoji} ${f.description}\n Categories: ${f.categories.join(', ')} | Severity: ${(f.severity * 100).toFixed(0)}%`;
97
91
  })
98
92
  .join('\n')
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
- export declare function createServer(): McpServer;
3
+ export declare function createServer(apiKey?: string): McpServer;
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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"}
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,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAsBvD"}
package/dist/src/index.js CHANGED
@@ -1,38 +1,35 @@
1
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) {
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { Tuteliq } from '@tuteliq/sdk';
4
+ import { registerDetectionTools } from './tools/detection.js';
5
+ import { registerFraudTools } from './tools/fraud.js';
6
+ import { registerMediaTools } from './tools/media.js';
7
+ import { registerAnalysisTools } from './tools/analysis.js';
8
+ import { registerAdminTools } from './tools/admin.js';
9
+ import { getTransportMode, startStdio } from './transport.js';
10
+ export function createServer(apiKey) {
11
+ const key = apiKey || process.env.TUTELIQ_API_KEY;
12
+ if (!key) {
16
13
  console.error('Error: TUTELIQ_API_KEY environment variable is required');
17
14
  process.exit(1);
18
15
  }
19
- const client = new sdk_1.Tuteliq(apiKey);
20
- const server = new mcp_js_1.McpServer({
16
+ const client = new Tuteliq(key);
17
+ const server = new McpServer({
21
18
  name: 'tuteliq-mcp',
22
- version: '3.0.0',
19
+ version: '3.2.0',
23
20
  });
24
21
  // 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);
22
+ registerDetectionTools(server, client);
23
+ registerFraudTools(server, client);
24
+ registerMediaTools(server, client);
25
+ registerAnalysisTools(server, client);
26
+ registerAdminTools(server, client);
30
27
  return server;
31
28
  }
32
29
  // Direct execution: stdio mode
33
- if ((0, transport_js_1.getTransportMode)() === 'stdio') {
30
+ if (getTransportMode() === 'stdio') {
34
31
  const server = createServer();
35
- (0, transport_js_1.startStdio)(server).catch((error) => {
32
+ startStdio(server).catch((error) => {
36
33
  console.error('Fatal error:', error);
37
34
  process.exit(1);
38
35
  });
@@ -1,9 +1,6 @@
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) {
1
+ import { z } from 'zod';
2
+ import { severityEmoji } from '../formatters.js';
3
+ export function registerAdminTools(server, client) {
7
4
  // =========================================================================
8
5
  // Webhook Management
9
6
  // =========================================================================
@@ -16,9 +13,9 @@ function registerAdminTools(server, client) {
16
13
  return { content: [{ type: 'text', text: `## Webhooks\n\n${lines}` }] };
17
14
  });
18
15
  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'),
16
+ name: z.string().describe('Display name for the webhook'),
17
+ url: z.string().describe('HTTPS URL to receive webhook payloads'),
18
+ events: z.array(z.string()).describe('Event types to subscribe to'),
22
19
  }, async ({ name, url, events }) => {
23
20
  const result = await client.createWebhook({
24
21
  name,
@@ -28,11 +25,11 @@ function registerAdminTools(server, client) {
28
25
  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
26
  });
30
27
  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'),
28
+ id: z.string().describe('Webhook ID'),
29
+ name: z.string().optional().describe('New display name'),
30
+ url: z.string().optional().describe('New HTTPS URL'),
31
+ events: z.array(z.string()).optional().describe('New event subscriptions'),
32
+ is_active: z.boolean().optional().describe('Enable or disable the webhook'),
36
33
  }, async ({ id, name, url, events, is_active }) => {
37
34
  const result = await client.updateWebhook(id, {
38
35
  name,
@@ -42,15 +39,15 @@ function registerAdminTools(server, client) {
42
39
  });
43
40
  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
41
  });
45
- server.tool('delete_webhook', 'Permanently delete a webhook.', { id: zod_1.z.string().describe('Webhook ID to delete') }, async ({ id }) => {
42
+ server.tool('delete_webhook', 'Permanently delete a webhook.', { id: z.string().describe('Webhook ID to delete') }, async ({ id }) => {
46
43
  await client.deleteWebhook(id);
47
44
  return { content: [{ type: 'text', text: `## \u2705 Webhook Deleted\n\nWebhook \`${id}\` has been permanently deleted.` }] };
48
45
  });
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 }) => {
46
+ server.tool('test_webhook', 'Send a test payload to a webhook to verify it is working correctly.', { id: z.string().describe('Webhook ID to test') }, async ({ id }) => {
50
47
  const result = await client.testWebhook(id);
51
48
  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
49
  });
53
- server.tool('regenerate_webhook_secret', 'Regenerate a webhook signing secret.', { id: zod_1.z.string().describe('Webhook ID') }, async ({ id }) => {
50
+ server.tool('regenerate_webhook_secret', 'Regenerate a webhook signing secret.', { id: z.string().describe('Webhook ID') }, async ({ id }) => {
54
51
  const result = await client.regenerateWebhookSecret(id);
55
52
  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
53
  });
@@ -70,7 +67,7 @@ function registerAdminTools(server, client) {
70
67
  // =========================================================================
71
68
  // Usage & Billing
72
69
  // =========================================================================
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 }) => {
70
+ server.tool('get_usage_history', 'Get daily usage history for the past N days.', { days: z.number().optional().describe('Number of days to retrieve (1-30, default: 7)') }, async ({ days }) => {
74
71
  const result = await client.getUsageHistory(days);
75
72
  if (result.days.length === 0) {
76
73
  return { content: [{ type: 'text', text: 'No usage data available.' }] };
@@ -78,7 +75,7 @@ function registerAdminTools(server, client) {
78
75
  const lines = result.days.map(d => `| ${d.date} | ${d.total_requests} | ${d.success_requests} | ${d.error_requests} |`).join('\n');
79
76
  return { content: [{ type: 'text', text: `## Usage History\n\n| Date | Total | Success | Errors |\n|------|-------|---------|--------|\n${lines}` }] };
80
77
  });
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 }) => {
78
+ server.tool('get_usage_by_tool', 'Get usage broken down by tool/endpoint for a specific date.', { date: z.string().optional().describe('Date in YYYY-MM-DD format (default: today)') }, async ({ date }) => {
82
79
  const result = await client.getUsageByTool(date);
83
80
  const toolLines = Object.entries(result.tools).map(([tool, count]) => `- **${tool}:** ${count}`).join('\n');
84
81
  const endpointLines = Object.entries(result.endpoints).map(([ep, count]) => `- **${ep}:** ${count}`).join('\n');
@@ -111,10 +108,10 @@ ${result.recommendations ? `### Recommendation\n${result.recommendations.reason}
111
108
  const collections = Object.keys(result.data).join(', ');
112
109
  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
110
  });
114
- const consentTypeEnum = zod_1.z.enum(['data_processing', 'analytics', 'marketing', 'third_party_sharing', 'child_safety_monitoring']);
111
+ const consentTypeEnum = z.enum(['data_processing', 'analytics', 'marketing', 'third_party_sharing', 'child_safety_monitoring']);
115
112
  server.tool('record_consent', 'Record user consent for data processing (GDPR Article 7).', {
116
113
  consent_type: consentTypeEnum.describe('Type of consent to record'),
117
- version: zod_1.z.string().describe('Policy version the user is consenting to'),
114
+ version: z.string().describe('Policy version the user is consenting to'),
118
115
  }, async ({ consent_type, version }) => {
119
116
  const result = await client.recordConsent({ consent_type: consent_type, version });
120
117
  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}` }] };
@@ -132,16 +129,16 @@ ${result.recommendations ? `### Recommendation\n${result.recommendations.reason}
132
129
  return { content: [{ type: 'text', text: `## \u26A0\uFE0F Consent Withdrawn\n\n**Type:** ${result.consent.consent_type}\n**Status:** ${result.consent.status}` }] };
133
130
  });
134
131
  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'),
132
+ collection: z.string().describe('Firestore collection name'),
133
+ document_id: z.string().describe('Document ID to rectify'),
134
+ fields: z.record(z.string(), z.unknown()).describe('Fields to update'),
138
135
  }, async ({ collection, document_id, fields }) => {
139
136
  const result = await client.rectifyData({ collection, document_id, fields: fields });
140
137
  return { content: [{ type: 'text', text: `## \u2705 Data Rectified\n\n**Message:** ${result.message}\n**Updated Fields:** ${result.updated_fields.join(', ')}` }] };
141
138
  });
142
139
  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'),
140
+ action: z.enum(['data_access', 'data_export', 'data_deletion', 'data_rectification', 'consent_granted', 'consent_withdrawn', 'breach_notification']).optional().describe('Filter by action type'),
141
+ limit: z.number().optional().describe('Maximum number of results'),
145
142
  }, async ({ action, limit }) => {
146
143
  const result = await client.getAuditLogs({ action: action, limit });
147
144
  if (result.audit_logs.length === 0) {
@@ -154,12 +151,12 @@ ${result.recommendations ? `### Recommendation\n${result.recommendations.reason}
154
151
  // Breach Management
155
152
  // =========================================================================
156
153
  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'),
154
+ title: z.string().describe('Brief title of the breach'),
155
+ description: z.string().describe('Detailed description'),
156
+ severity: z.enum(['low', 'medium', 'high', 'critical']).describe('Breach severity'),
157
+ affected_user_ids: z.array(z.string()).describe('List of affected user IDs'),
158
+ data_categories: z.array(z.string()).describe('Categories of data affected'),
159
+ reported_by: z.string().describe('Who reported the breach'),
163
160
  }, async ({ title, description, severity, affected_user_ids, data_categories, reported_by }) => {
164
161
  const result = await client.logBreach({
165
162
  title,
@@ -170,30 +167,30 @@ ${result.recommendations ? `### Recommendation\n${result.recommendations.reason}
170
167
  reported_by,
171
168
  });
172
169
  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(', ')}` }] };
170
+ return { content: [{ type: 'text', text: `## \u26A0\uFE0F Breach Logged\n\n**ID:** ${b.id}\n**Title:** ${b.title}\n**Severity:** ${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
171
  });
175
- const breachStatusEnum = zod_1.z.enum(['detected', 'investigating', 'contained', 'reported', 'resolved']);
172
+ const breachStatusEnum = z.enum(['detected', 'investigating', 'contained', 'reported', 'resolved']);
176
173
  server.tool('list_breaches', 'List all data breaches.', {
177
174
  status: breachStatusEnum.optional().describe('Filter by status'),
178
- limit: zod_1.z.number().optional().describe('Maximum number of results'),
175
+ limit: z.number().optional().describe('Maximum number of results'),
179
176
  }, async ({ status, limit }) => {
180
177
  const result = await client.listBreaches({ status: status, limit });
181
178
  if (result.breaches.length === 0) {
182
179
  return { content: [{ type: 'text', text: 'No breaches found.' }] };
183
180
  }
184
- const breachLines = result.breaches.map(b => `- ${formatters_js_1.severityEmoji[b.severity] || '\u26AA'} **${b.title}** \u2014 ${b.status} _(${b.id})_`).join('\n');
181
+ const breachLines = result.breaches.map(b => `- ${severityEmoji[b.severity] || '\u26AA'} **${b.title}** \u2014 ${b.status} _(${b.id})_`).join('\n');
185
182
  return { content: [{ type: 'text', text: `## Data Breaches\n\n${breachLines}` }] };
186
183
  });
187
- server.tool('get_breach', 'Get details of a specific data breach.', { id: zod_1.z.string().describe('Breach ID') }, async ({ id }) => {
184
+ server.tool('get_breach', 'Get details of a specific data breach.', { id: z.string().describe('Breach ID') }, async ({ id }) => {
188
185
  const result = await client.getBreach(id);
189
186
  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(', ')}` }] };
187
+ return { content: [{ type: 'text', text: `## Breach Details\n\n**ID:** ${b.id}\n**Title:** ${b.title}\n**Severity:** ${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
188
  });
192
189
  server.tool('update_breach_status', 'Update a breach status and notification progress.', {
193
- id: zod_1.z.string().describe('Breach ID'),
190
+ id: z.string().describe('Breach ID'),
194
191
  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'),
192
+ notification_status: z.enum(['pending', 'users_notified', 'dpa_notified', 'completed']).optional().describe('Notification progress'),
193
+ notes: z.string().optional().describe('Additional notes'),
197
194
  }, async ({ id, status, notification_status, notes }) => {
198
195
  const result = await client.updateBreachStatus(id, {
199
196
  status: status,
@@ -1,19 +1,16 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerAnalysisTools = registerAnalysisTools;
4
- const zod_1 = require("zod");
5
- const server_1 = require("@modelcontextprotocol/ext-apps/server");
6
- const fs_1 = require("fs");
7
- const path_1 = require("path");
8
- const formatters_js_1 = require("../formatters.js");
1
+ import { z } from 'zod';
2
+ import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server';
3
+ import { readFileSync } from 'fs';
4
+ import { resolve } from 'path';
5
+ import { trendEmoji, riskEmoji, formatMultiResult } from '../formatters.js';
9
6
  const EMOTIONS_WIDGET_URI = 'ui://tuteliq/emotions-result.html';
10
7
  const ACTION_PLAN_WIDGET_URI = 'ui://tuteliq/action-plan.html';
11
8
  const REPORT_WIDGET_URI = 'ui://tuteliq/report-result.html';
12
9
  const MULTI_WIDGET_URI = 'ui://tuteliq/multi-result.html';
13
10
  function loadWidget(name) {
14
- return (0, fs_1.readFileSync)((0, path_1.resolve)(__dirname, '../../dist-ui', name), 'utf-8');
11
+ return readFileSync(resolve(__dirname, '../../dist-ui', name), 'utf-8');
15
12
  }
16
- function registerAnalysisTools(server, client) {
13
+ export function registerAnalysisTools(server, client) {
17
14
  const resources = [
18
15
  { uri: EMOTIONS_WIDGET_URI, file: 'emotions-result.html' },
19
16
  { uri: ACTION_PLAN_WIDGET_URI, file: 'action-plan.html' },
@@ -21,21 +18,21 @@ function registerAnalysisTools(server, client) {
21
18
  { uri: MULTI_WIDGET_URI, file: 'multi-result.html' },
22
19
  ];
23
20
  for (const r of resources) {
24
- (0, server_1.registerAppResource)(server, r.uri, r.uri, { mimeType: server_1.RESOURCE_MIME_TYPE }, async () => ({
21
+ registerAppResource(server, r.uri, r.uri, { mimeType: RESOURCE_MIME_TYPE }, async () => ({
25
22
  contents: [{
26
23
  uri: r.uri,
27
- mimeType: server_1.RESOURCE_MIME_TYPE,
24
+ mimeType: RESOURCE_MIME_TYPE,
28
25
  text: loadWidget(r.file),
29
26
  }],
30
27
  }));
31
28
  }
32
29
  // ── analyze_emotions ───────────────────────────────────────────────────────
33
- (0, server_1.registerAppTool)(server, 'analyze_emotions', {
30
+ registerAppTool(server, 'analyze_emotions', {
34
31
  title: 'Analyze Emotions',
35
32
  description: 'Analyze emotional content and mental state indicators. Identifies dominant emotions, trends, and provides follow-up recommendations.',
36
33
  annotations: { readOnlyHint: true, openWorldHint: true, destructiveHint: false },
37
34
  inputSchema: {
38
- content: zod_1.z.string().describe('The text content to analyze for emotions'),
35
+ content: z.string().describe('The text content to analyze for emotions'),
39
36
  },
40
37
  _meta: {
41
38
  ui: { resourceUri: EMOTIONS_WIDGET_URI },
@@ -45,7 +42,7 @@ function registerAnalysisTools(server, client) {
45
42
  },
46
43
  }, async ({ content }) => {
47
44
  const result = await client.analyzeEmotions({ content });
48
- const emoji = formatters_js_1.trendEmoji[result.trend] || '\u27A1\uFE0F';
45
+ const emoji = trendEmoji[result.trend] || '\u27A1\uFE0F';
49
46
  const emotionScoresList = Object.entries(result.emotion_scores)
50
47
  .sort((a, b) => b[1] - a[1])
51
48
  .map(([emotion, score]) => `- ${emotion}: ${(score * 100).toFixed(0)}%`)
@@ -69,15 +66,15 @@ ${result.recommended_followup}`;
69
66
  };
70
67
  });
71
68
  // ── get_action_plan ────────────────────────────────────────────────────────
72
- (0, server_1.registerAppTool)(server, 'get_action_plan', {
69
+ registerAppTool(server, 'get_action_plan', {
73
70
  title: 'Get Action Plan',
74
71
  description: 'Generate age-appropriate guidance and action steps for handling a safety situation.',
75
72
  annotations: { readOnlyHint: true, openWorldHint: true, destructiveHint: false },
76
73
  inputSchema: {
77
- situation: zod_1.z.string().describe('Description of the situation needing guidance'),
78
- childAge: zod_1.z.number().optional().describe('Age of the child involved'),
79
- audience: zod_1.z.enum(['child', 'parent', 'educator', 'platform']).optional().describe('Who the guidance is for (default: parent)'),
80
- severity: zod_1.z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Severity of the situation'),
74
+ situation: z.string().describe('Description of the situation needing guidance'),
75
+ childAge: z.number().optional().describe('Age of the child involved'),
76
+ audience: z.enum(['child', 'parent', 'educator', 'platform']).optional().describe('Who the guidance is for (default: parent)'),
77
+ severity: z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Severity of the situation'),
81
78
  },
82
79
  _meta: {
83
80
  ui: { resourceUri: ACTION_PLAN_WIDGET_URI },
@@ -101,17 +98,17 @@ ${result.steps.map((step, i) => `${i + 1}. ${step}`).join('\n')}`;
101
98
  };
102
99
  });
103
100
  // ── generate_report ────────────────────────────────────────────────────────
104
- (0, server_1.registerAppTool)(server, 'generate_report', {
101
+ registerAppTool(server, 'generate_report', {
105
102
  title: 'Generate Report',
106
103
  description: 'Generate a comprehensive incident report from a conversation.',
107
104
  annotations: { readOnlyHint: true, openWorldHint: true, destructiveHint: false },
108
105
  inputSchema: {
109
- messages: zod_1.z.array(zod_1.z.object({
110
- sender: zod_1.z.string().describe('Name/ID of sender'),
111
- content: zod_1.z.string().describe('Message content'),
106
+ messages: z.array(z.object({
107
+ sender: z.string().describe('Name/ID of sender'),
108
+ content: z.string().describe('Message content'),
112
109
  })).describe('Array of messages in the incident'),
113
- childAge: zod_1.z.number().optional().describe('Age of the child involved'),
114
- incidentType: zod_1.z.string().optional().describe('Type of incident (e.g., bullying, grooming)'),
110
+ childAge: z.number().optional().describe('Age of the child involved'),
111
+ incidentType: z.string().optional().describe('Type of incident (e.g., bullying, grooming)'),
115
112
  },
116
113
  _meta: {
117
114
  ui: { resourceUri: REPORT_WIDGET_URI },
@@ -125,7 +122,7 @@ ${result.steps.map((step, i) => `${i + 1}. ${step}`).join('\n')}`;
125
122
  childAge,
126
123
  incident: incidentType ? { type: incidentType } : undefined,
127
124
  });
128
- const emoji = formatters_js_1.riskEmoji[result.risk_level] || '\u26AA';
125
+ const emoji = riskEmoji[result.risk_level] || '\u26AA';
129
126
  const text = `## \u{1F4CB} Incident Report
130
127
 
131
128
  **Risk Level:** ${emoji} ${result.risk_level.charAt(0).toUpperCase() + result.risk_level.slice(1)}
@@ -144,17 +141,17 @@ ${result.recommended_next_steps.map((step, i) => `${i + 1}. ${step}`).join('\n')
144
141
  };
145
142
  });
146
143
  // ── analyse_multi ──────────────────────────────────────────────────────────
147
- (0, server_1.registerAppTool)(server, 'analyse_multi', {
144
+ registerAppTool(server, 'analyse_multi', {
148
145
  title: 'Multi-Endpoint Analysis',
149
146
  description: 'Run multiple detection endpoints on a single piece of text.',
150
147
  annotations: { readOnlyHint: true, openWorldHint: true, destructiveHint: false },
151
148
  inputSchema: {
152
- content: zod_1.z.string().describe('Text content to analyze'),
153
- endpoints: zod_1.z.array(zod_1.z.string()).describe('Detection endpoints to run'),
154
- context: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional().describe('Optional analysis context'),
155
- include_evidence: zod_1.z.boolean().optional().describe('Include supporting evidence'),
156
- external_id: zod_1.z.string().optional().describe('External tracking ID'),
157
- customer_id: zod_1.z.string().optional().describe('Customer identifier'),
149
+ content: z.string().describe('Text content to analyze'),
150
+ endpoints: z.array(z.string()).describe('Detection endpoints to run'),
151
+ context: z.record(z.string(), z.unknown()).optional().describe('Optional analysis context'),
152
+ include_evidence: z.boolean().optional().describe('Include supporting evidence'),
153
+ external_id: z.string().optional().describe('External tracking ID'),
154
+ customer_id: z.string().optional().describe('Customer identifier'),
158
155
  },
159
156
  _meta: {
160
157
  ui: { resourceUri: MULTI_WIDGET_URI },
@@ -173,7 +170,7 @@ ${result.recommended_next_steps.map((step, i) => `${i + 1}. ${step}`).join('\n')
173
170
  });
174
171
  return {
175
172
  structuredContent: { toolName: 'analyse_multi', result, branding: { appName: 'Tuteliq' } },
176
- content: [{ type: 'text', text: (0, formatters_js_1.formatMultiResult)(result) }],
173
+ content: [{ type: 'text', text: formatMultiResult(result) }],
177
174
  };
178
175
  });
179
176
  }