@tuteliq/mcp 3.2.4 → 3.2.6

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.
@@ -13,6 +13,21 @@ function loadWidget(name) {
13
13
  function filenameFromPath(filePath) {
14
14
  return filePath.split('/').pop() || filePath;
15
15
  }
16
+ function handleTierError(err, toolName, featureLabel) {
17
+ if (err?.status === 403 || err?.response?.status === 403) {
18
+ const upsellResult = {
19
+ error: 'tier_restricted',
20
+ tier_restricted: true,
21
+ upgrade: true,
22
+ message: `Your current plan does not include ${featureLabel.toLowerCase()}. Upgrade your plan or purchase additional credits to unlock this feature.`,
23
+ };
24
+ return {
25
+ structuredContent: { toolName, result: upsellResult, branding: { appName: 'Tuteliq' } },
26
+ content: [{ type: 'text', text: `\u26A0\uFE0F ${upsellResult.message}\n\nUpgrade at: https://tuteliq.ai/dashboard` }],
27
+ };
28
+ }
29
+ return null;
30
+ }
16
31
  export function registerMediaTools(server, client) {
17
32
  registerAppResource(server, MEDIA_WIDGET_URI, MEDIA_WIDGET_URI, { mimeType: RESOURCE_MIME_TYPE }, async () => ({
18
33
  contents: [{
@@ -39,34 +54,35 @@ export function registerMediaTools(server, client) {
39
54
  'openai/toolInvocation/invoked': 'Voice analysis complete.',
40
55
  },
41
56
  }, async ({ file_path, analysis_type, child_age, language }) => {
42
- const buffer = readFileSync(file_path);
43
- const filename = filenameFromPath(file_path);
44
- const result = await client.analyzeVoice({
45
- file: buffer,
46
- filename,
47
- analysisType: analysis_type || 'all',
48
- language,
49
- childAge: child_age,
50
- });
51
- const emoji = severityEmoji[result.overall_severity] || '\u2705';
52
- const segmentLines = result.transcription.segments
53
- .slice(0, 20)
54
- .map(s => `\`${s.start.toFixed(1)}s\u2013${s.end.toFixed(1)}s\` ${s.text}`)
55
- .join('\n');
56
- const analysisLines = [];
57
- if (result.analysis.bullying) {
58
- analysisLines.push(`**Bullying:** ${result.analysis.bullying.is_bullying ? '\u26A0\uFE0F Detected' : '\u2705 Clear'} (${(result.analysis.bullying.risk_score * 100).toFixed(0)}%)`);
59
- }
60
- if (result.analysis.unsafe) {
61
- analysisLines.push(`**Unsafe:** ${result.analysis.unsafe.unsafe ? '\u26A0\uFE0F Detected' : '\u2705 Clear'} (${(result.analysis.unsafe.risk_score * 100).toFixed(0)}%)`);
62
- }
63
- if (result.analysis.grooming) {
64
- analysisLines.push(`**Grooming:** ${result.analysis.grooming.grooming_risk !== 'none' ? '\u26A0\uFE0F ' + result.analysis.grooming.grooming_risk : '\u2705 Clear'} (${(result.analysis.grooming.risk_score * 100).toFixed(0)}%)`);
65
- }
66
- if (result.analysis.emotions) {
67
- analysisLines.push(`**Emotions:** ${result.analysis.emotions.dominant_emotions.join(', ')} (${trendEmoji[result.analysis.emotions.trend] || ''} ${result.analysis.emotions.trend})`);
68
- }
69
- const text = `## \u{1F399}\uFE0F Voice Analysis
57
+ try {
58
+ const buffer = readFileSync(file_path);
59
+ const filename = filenameFromPath(file_path);
60
+ const result = await client.analyzeVoice({
61
+ file: buffer,
62
+ filename,
63
+ analysisType: analysis_type || 'all',
64
+ language,
65
+ childAge: child_age,
66
+ });
67
+ const emoji = severityEmoji[result.overall_severity] || '\u2705';
68
+ const segmentLines = result.transcription.segments
69
+ .slice(0, 20)
70
+ .map(s => `\`${s.start.toFixed(1)}s\u2013${s.end.toFixed(1)}s\` ${s.text}`)
71
+ .join('\n');
72
+ const analysisLines = [];
73
+ if (result.analysis.bullying) {
74
+ analysisLines.push(`**Bullying:** ${result.analysis.bullying.is_bullying ? '\u26A0\uFE0F Detected' : '\u2705 Clear'} (${(result.analysis.bullying.risk_score * 100).toFixed(0)}%)`);
75
+ }
76
+ if (result.analysis.unsafe) {
77
+ analysisLines.push(`**Unsafe:** ${result.analysis.unsafe.unsafe ? '\u26A0\uFE0F Detected' : '\u2705 Clear'} (${(result.analysis.unsafe.risk_score * 100).toFixed(0)}%)`);
78
+ }
79
+ if (result.analysis.grooming) {
80
+ analysisLines.push(`**Grooming:** ${result.analysis.grooming.grooming_risk !== 'none' ? '\u26A0\uFE0F ' + result.analysis.grooming.grooming_risk : '\u2705 Clear'} (${(result.analysis.grooming.risk_score * 100).toFixed(0)}%)`);
81
+ }
82
+ if (result.analysis.emotions) {
83
+ analysisLines.push(`**Emotions:** ${result.analysis.emotions.dominant_emotions.join(', ')} (${trendEmoji[result.analysis.emotions.trend] || ''} ${result.analysis.emotions.trend})`);
84
+ }
85
+ const text = `## \u{1F399}\uFE0F Voice Analysis
70
86
 
71
87
  **Overall Severity:** ${emoji} ${result.overall_severity}
72
88
  **Overall Risk Score:** ${(result.overall_risk_score * 100).toFixed(0)}%
@@ -81,10 +97,17 @@ ${segmentLines}${result.transcription.segments.length > 20 ? `\n_...and ${result
81
97
 
82
98
  ### Analysis Results
83
99
  ${analysisLines.join('\n')}`;
84
- return {
85
- structuredContent: { toolName: 'analyze_voice', result, branding: { appName: 'Tuteliq' } },
86
- content: [{ type: 'text', text }],
87
- };
100
+ return {
101
+ structuredContent: { toolName: 'analyze_voice', result, branding: { appName: 'Tuteliq' } },
102
+ content: [{ type: 'text', text }],
103
+ };
104
+ }
105
+ catch (err) {
106
+ const upsell = handleTierError(err, 'analyze_voice', 'Voice Analysis');
107
+ if (upsell)
108
+ return upsell;
109
+ throw err;
110
+ }
88
111
  });
89
112
  // ── analyze_image ──────────────────────────────────────────────────────────
90
113
  registerAppTool(server, 'analyze_image', {
@@ -102,25 +125,26 @@ ${analysisLines.join('\n')}`;
102
125
  'openai/toolInvocation/invoked': 'Image analysis complete.',
103
126
  },
104
127
  }, async ({ file_path, analysis_type }) => {
105
- const buffer = readFileSync(file_path);
106
- const filename = filenameFromPath(file_path);
107
- const result = await client.analyzeImage({
108
- file: buffer,
109
- filename,
110
- analysisType: analysis_type || 'all',
111
- });
112
- const emoji = severityEmoji[result.overall_severity] || '\u2705';
113
- const textAnalysisLines = [];
114
- if (result.text_analysis?.bullying) {
115
- textAnalysisLines.push(`**Bullying:** ${result.text_analysis.bullying.is_bullying ? '\u26A0\uFE0F Detected' : '\u2705 Clear'} (${(result.text_analysis.bullying.risk_score * 100).toFixed(0)}%)`);
116
- }
117
- if (result.text_analysis?.unsafe) {
118
- textAnalysisLines.push(`**Unsafe:** ${result.text_analysis.unsafe.unsafe ? '\u26A0\uFE0F Detected' : '\u2705 Clear'} (${(result.text_analysis.unsafe.risk_score * 100).toFixed(0)}%)`);
119
- }
120
- if (result.text_analysis?.emotions) {
121
- textAnalysisLines.push(`**Emotions:** ${result.text_analysis.emotions.dominant_emotions.join(', ')}`);
122
- }
123
- const text = `## \u{1F5BC}\uFE0F Image Analysis
128
+ try {
129
+ const buffer = readFileSync(file_path);
130
+ const filename = filenameFromPath(file_path);
131
+ const result = await client.analyzeImage({
132
+ file: buffer,
133
+ filename,
134
+ analysisType: analysis_type || 'all',
135
+ });
136
+ const emoji = severityEmoji[result.overall_severity] || '\u2705';
137
+ const textAnalysisLines = [];
138
+ if (result.text_analysis?.bullying) {
139
+ textAnalysisLines.push(`**Bullying:** ${result.text_analysis.bullying.is_bullying ? '\u26A0\uFE0F Detected' : '\u2705 Clear'} (${(result.text_analysis.bullying.risk_score * 100).toFixed(0)}%)`);
140
+ }
141
+ if (result.text_analysis?.unsafe) {
142
+ textAnalysisLines.push(`**Unsafe:** ${result.text_analysis.unsafe.unsafe ? '\u26A0\uFE0F Detected' : '\u2705 Clear'} (${(result.text_analysis.unsafe.risk_score * 100).toFixed(0)}%)`);
143
+ }
144
+ if (result.text_analysis?.emotions) {
145
+ textAnalysisLines.push(`**Emotions:** ${result.text_analysis.emotions.dominant_emotions.join(', ')}`);
146
+ }
147
+ const text = `## \u{1F5BC}\uFE0F Image Analysis
124
148
 
125
149
  **Overall Severity:** ${emoji} ${result.overall_severity}
126
150
  **Overall Risk Score:** ${(result.overall_risk_score * 100).toFixed(0)}%
@@ -136,10 +160,17 @@ ${result.vision.visual_categories.length > 0 ? `**Visual Categories:** ${result.
136
160
  ${result.vision.extracted_text ? `### Extracted Text (OCR)\n${result.vision.extracted_text}` : ''}
137
161
 
138
162
  ${textAnalysisLines.length > 0 ? `### Text Analysis Results\n${textAnalysisLines.join('\n')}` : ''}`;
139
- return {
140
- structuredContent: { toolName: 'analyze_image', result, branding: { appName: 'Tuteliq' } },
141
- content: [{ type: 'text', text }],
142
- };
163
+ return {
164
+ structuredContent: { toolName: 'analyze_image', result, branding: { appName: 'Tuteliq' } },
165
+ content: [{ type: 'text', text }],
166
+ };
167
+ }
168
+ catch (err) {
169
+ const upsell = handleTierError(err, 'analyze_image', 'Image Analysis');
170
+ if (upsell)
171
+ return upsell;
172
+ throw err;
173
+ }
143
174
  });
144
175
  // ── analyze_video ──────────────────────────────────────────────────────────
145
176
  registerAppTool(server, 'analyze_video', {
@@ -157,16 +188,24 @@ ${textAnalysisLines.length > 0 ? `### Text Analysis Results\n${textAnalysisLines
157
188
  'openai/toolInvocation/invoked': 'Video analysis complete.',
158
189
  },
159
190
  }, async ({ file_path, age_group }) => {
160
- const buffer = readFileSync(file_path);
161
- const filename = filenameFromPath(file_path);
162
- const result = await client.analyzeVideo({
163
- file: buffer,
164
- filename,
165
- ageGroup: age_group,
166
- });
167
- return {
168
- structuredContent: { toolName: 'analyze_video', result, branding: { appName: 'Tuteliq' } },
169
- content: [{ type: 'text', text: formatVideoResult(result) }],
170
- };
191
+ try {
192
+ const buffer = readFileSync(file_path);
193
+ const filename = filenameFromPath(file_path);
194
+ const result = await client.analyzeVideo({
195
+ file: buffer,
196
+ filename,
197
+ ageGroup: age_group,
198
+ });
199
+ return {
200
+ structuredContent: { toolName: 'analyze_video', result, branding: { appName: 'Tuteliq' } },
201
+ content: [{ type: 'text', text: formatVideoResult(result) }],
202
+ };
203
+ }
204
+ catch (err) {
205
+ const upsell = handleTierError(err, 'analyze_video', 'Video Analysis');
206
+ if (upsell)
207
+ return upsell;
208
+ throw err;
209
+ }
171
210
  });
172
211
  }