@rlabs-inc/gemini-mcp 0.5.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 (39) hide show
  1. package/LICENCE +21 -0
  2. package/README.md +418 -0
  3. package/dist/gemini-client.d.ts +120 -0
  4. package/dist/gemini-client.js +399 -0
  5. package/dist/index.d.ts +8 -0
  6. package/dist/index.js +220 -0
  7. package/dist/tools/analyze.d.ts +10 -0
  8. package/dist/tools/analyze.js +96 -0
  9. package/dist/tools/brainstorm.d.ts +10 -0
  10. package/dist/tools/brainstorm.js +220 -0
  11. package/dist/tools/cache.d.ts +17 -0
  12. package/dist/tools/cache.js +286 -0
  13. package/dist/tools/code-exec.d.ts +17 -0
  14. package/dist/tools/code-exec.js +135 -0
  15. package/dist/tools/document.d.ts +16 -0
  16. package/dist/tools/document.js +333 -0
  17. package/dist/tools/image-edit.d.ts +16 -0
  18. package/dist/tools/image-edit.js +291 -0
  19. package/dist/tools/image-gen.d.ts +17 -0
  20. package/dist/tools/image-gen.js +148 -0
  21. package/dist/tools/query.d.ts +11 -0
  22. package/dist/tools/query.js +63 -0
  23. package/dist/tools/search.d.ts +15 -0
  24. package/dist/tools/search.js +128 -0
  25. package/dist/tools/speech.d.ts +17 -0
  26. package/dist/tools/speech.js +304 -0
  27. package/dist/tools/structured.d.ts +16 -0
  28. package/dist/tools/structured.js +247 -0
  29. package/dist/tools/summarize.d.ts +10 -0
  30. package/dist/tools/summarize.js +77 -0
  31. package/dist/tools/url-context.d.ts +17 -0
  32. package/dist/tools/url-context.js +226 -0
  33. package/dist/tools/video-gen.d.ts +11 -0
  34. package/dist/tools/video-gen.js +136 -0
  35. package/dist/tools/youtube.d.ts +16 -0
  36. package/dist/tools/youtube.js +218 -0
  37. package/dist/utils/logger.d.ts +33 -0
  38. package/dist/utils/logger.js +82 -0
  39. package/package.json +48 -0
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Speech Generation Tool - Text-to-Speech with Gemini
3
+ *
4
+ * Generate high-quality speech from text using Gemini's native TTS.
5
+ * Features:
6
+ * - 30 voice options with different tones and styles
7
+ * - Multi-speaker support (up to 2 speakers)
8
+ * - Controllable style, accent, pace via natural language
9
+ * - 24 language support
10
+ *
11
+ * Output: WAV files saved to the output directory
12
+ */
13
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
14
+ /**
15
+ * Register speech generation tools with the MCP server
16
+ */
17
+ export declare function registerSpeechTool(server: McpServer): void;
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Speech Generation Tool - Text-to-Speech with Gemini
3
+ *
4
+ * Generate high-quality speech from text using Gemini's native TTS.
5
+ * Features:
6
+ * - 30 voice options with different tones and styles
7
+ * - Multi-speaker support (up to 2 speakers)
8
+ * - Controllable style, accent, pace via natural language
9
+ * - 24 language support
10
+ *
11
+ * Output: WAV files saved to the output directory
12
+ */
13
+ import { z } from 'zod';
14
+ import { GoogleGenAI, Modality } from '@google/genai';
15
+ import { logger } from '../utils/logger.js';
16
+ import * as fs from 'fs';
17
+ import * as path from 'path';
18
+ // Get output directory for generated files
19
+ function getOutputDir() {
20
+ return (process.env.GEMINI_OUTPUT_DIR || path.join(process.cwd(), 'gemini-output'));
21
+ }
22
+ // Save PCM audio as WAV file
23
+ function saveWavFile(filename, pcmData, channels = 1, sampleRate = 24000, bitsPerSample = 16) {
24
+ const outputDir = getOutputDir();
25
+ if (!fs.existsSync(outputDir)) {
26
+ fs.mkdirSync(outputDir, { recursive: true });
27
+ }
28
+ const filePath = path.join(outputDir, filename);
29
+ // Create WAV header
30
+ const byteRate = (sampleRate * channels * bitsPerSample) / 8;
31
+ const blockAlign = (channels * bitsPerSample) / 8;
32
+ const dataSize = pcmData.length;
33
+ const headerSize = 44;
34
+ const fileSize = headerSize + dataSize - 8;
35
+ const header = Buffer.alloc(headerSize);
36
+ // RIFF header
37
+ header.write('RIFF', 0);
38
+ header.writeUInt32LE(fileSize, 4);
39
+ header.write('WAVE', 8);
40
+ // fmt chunk
41
+ header.write('fmt ', 12);
42
+ header.writeUInt32LE(16, 16); // chunk size
43
+ header.writeUInt16LE(1, 20); // audio format (PCM)
44
+ header.writeUInt16LE(channels, 22);
45
+ header.writeUInt32LE(sampleRate, 24);
46
+ header.writeUInt32LE(byteRate, 28);
47
+ header.writeUInt16LE(blockAlign, 32);
48
+ header.writeUInt16LE(bitsPerSample, 34);
49
+ // data chunk
50
+ header.write('data', 36);
51
+ header.writeUInt32LE(dataSize, 40);
52
+ // Write file
53
+ fs.writeFileSync(filePath, Buffer.concat([header, pcmData]));
54
+ return filePath;
55
+ }
56
+ // Available voices
57
+ const VOICES = [
58
+ 'Zephyr',
59
+ 'Puck',
60
+ 'Charon',
61
+ 'Kore',
62
+ 'Fenrir',
63
+ 'Leda',
64
+ 'Orus',
65
+ 'Aoede',
66
+ 'Callirrhoe',
67
+ 'Autonoe',
68
+ 'Enceladus',
69
+ 'Iapetus',
70
+ 'Umbriel',
71
+ 'Algieba',
72
+ 'Despina',
73
+ 'Erinome',
74
+ 'Algenib',
75
+ 'Rasalgethi',
76
+ 'Laomedeia',
77
+ 'Achernar',
78
+ 'Alnilam',
79
+ 'Schedar',
80
+ 'Gacrux',
81
+ 'Pulcherrima',
82
+ 'Achird',
83
+ 'Zubenelgenubi',
84
+ 'Vindemiatrix',
85
+ 'Sadachbia',
86
+ 'Sadaltager',
87
+ 'Sulafat',
88
+ ];
89
+ /**
90
+ * Register speech generation tools with the MCP server
91
+ */
92
+ export function registerSpeechTool(server) {
93
+ // Single speaker TTS
94
+ server.tool('gemini-speak', {
95
+ text: z.string().describe('The text to convert to speech'),
96
+ voice: z
97
+ .enum(VOICES)
98
+ .default('Kore')
99
+ .describe('Voice to use. Popular: Kore (firm), Puck (upbeat), Zephyr (bright), Charon (informative), Aoede (breezy)'),
100
+ style: z
101
+ .string()
102
+ .optional()
103
+ .describe('Style instructions (e.g., "cheerfully", "in a spooky whisper", "with excitement")'),
104
+ }, async ({ text, voice, style }) => {
105
+ logger.info(`Speech generation: ${text.substring(0, 50)}...`);
106
+ try {
107
+ const apiKey = process.env.GEMINI_API_KEY;
108
+ if (!apiKey) {
109
+ throw new Error('GEMINI_API_KEY not set');
110
+ }
111
+ const genAI = new GoogleGenAI({ apiKey });
112
+ const model = 'gemini-2.5-flash-preview-tts';
113
+ // Build prompt with optional style
114
+ const prompt = style ? `Say ${style}: "${text}"` : text;
115
+ // Generate speech
116
+ const response = await genAI.models.generateContent({
117
+ model,
118
+ contents: [{ parts: [{ text: prompt }] }],
119
+ config: {
120
+ responseModalities: [Modality.AUDIO],
121
+ speechConfig: {
122
+ voiceConfig: {
123
+ prebuiltVoiceConfig: { voiceName: voice },
124
+ },
125
+ },
126
+ },
127
+ });
128
+ // Extract audio data
129
+ const audioData = response.candidates?.[0]?.content?.parts?.[0]?.inlineData?.data;
130
+ if (!audioData) {
131
+ throw new Error('No audio data in response');
132
+ }
133
+ // Save as WAV
134
+ const audioBuffer = Buffer.from(audioData, 'base64');
135
+ const timestamp = Date.now();
136
+ const filename = `speech-${timestamp}.wav`;
137
+ const filePath = saveWavFile(filename, audioBuffer);
138
+ logger.info(`Speech saved to: ${filePath}`);
139
+ return {
140
+ content: [
141
+ {
142
+ type: 'text',
143
+ text: `Speech generated successfully!\n\n**Voice:** ${voice}\n**File:** ${filePath}\n**Duration:** ~${Math.round(audioBuffer.length / 48000)}s\n\nThe audio file has been saved and can be played with any audio player.`,
144
+ },
145
+ ],
146
+ };
147
+ }
148
+ catch (error) {
149
+ const errorMessage = error instanceof Error ? error.message : String(error);
150
+ logger.error(`Error in speech generation: ${errorMessage}`);
151
+ return {
152
+ content: [
153
+ {
154
+ type: 'text',
155
+ text: `Error generating speech: ${errorMessage}`,
156
+ },
157
+ ],
158
+ isError: true,
159
+ };
160
+ }
161
+ });
162
+ // Multi-speaker TTS (for dialogues/podcasts)
163
+ server.tool('gemini-dialogue', {
164
+ script: z
165
+ .string()
166
+ .describe('The dialogue script with speaker names. Format: "Speaker1: line\\nSpeaker2: line"'),
167
+ speaker1: z.string().describe('Name of first speaker as used in script'),
168
+ speaker1Voice: z
169
+ .enum(VOICES)
170
+ .default('Kore')
171
+ .describe('Voice for speaker 1'),
172
+ speaker2: z.string().describe('Name of second speaker as used in script'),
173
+ speaker2Voice: z
174
+ .enum(VOICES)
175
+ .default('Puck')
176
+ .describe('Voice for speaker 2'),
177
+ style: z
178
+ .string()
179
+ .optional()
180
+ .describe('Style instructions for the dialogue (e.g., "Make Speaker1 sound tired, Speaker2 excited")'),
181
+ }, async ({ script, speaker1, speaker1Voice, speaker2, speaker2Voice, style, }) => {
182
+ logger.info(`Dialogue generation: ${speaker1} & ${speaker2}`);
183
+ try {
184
+ const apiKey = process.env.GEMINI_API_KEY;
185
+ if (!apiKey) {
186
+ throw new Error('GEMINI_API_KEY not set');
187
+ }
188
+ const genAI = new GoogleGenAI({ apiKey });
189
+ const model = 'gemini-2.5-flash-preview-tts';
190
+ // Build prompt
191
+ let prompt = script;
192
+ if (style) {
193
+ prompt = `${style}\n\n${script}`;
194
+ }
195
+ // Generate multi-speaker speech
196
+ const response = await genAI.models.generateContent({
197
+ model,
198
+ contents: [{ parts: [{ text: prompt }] }],
199
+ config: {
200
+ responseModalities: [Modality.AUDIO],
201
+ speechConfig: {
202
+ multiSpeakerVoiceConfig: {
203
+ speakerVoiceConfigs: [
204
+ {
205
+ speaker: speaker1,
206
+ voiceConfig: {
207
+ prebuiltVoiceConfig: { voiceName: speaker1Voice },
208
+ },
209
+ },
210
+ {
211
+ speaker: speaker2,
212
+ voiceConfig: {
213
+ prebuiltVoiceConfig: { voiceName: speaker2Voice },
214
+ },
215
+ },
216
+ ],
217
+ },
218
+ },
219
+ },
220
+ });
221
+ // Extract audio data
222
+ const audioData = response.candidates?.[0]?.content?.parts?.[0]?.inlineData?.data;
223
+ if (!audioData) {
224
+ throw new Error('No audio data in response');
225
+ }
226
+ // Save as WAV
227
+ const audioBuffer = Buffer.from(audioData, 'base64');
228
+ const timestamp = Date.now();
229
+ const filename = `dialogue-${timestamp}.wav`;
230
+ const filePath = saveWavFile(filename, audioBuffer);
231
+ logger.info(`Dialogue saved to: ${filePath}`);
232
+ return {
233
+ content: [
234
+ {
235
+ type: 'text',
236
+ text: `Dialogue generated successfully!\n\n**Speakers:**\n- ${speaker1}: ${speaker1Voice}\n- ${speaker2}: ${speaker2Voice}\n\n**File:** ${filePath}\n**Duration:** ~${Math.round(audioBuffer.length / 48000)}s`,
237
+ },
238
+ ],
239
+ };
240
+ }
241
+ catch (error) {
242
+ const errorMessage = error instanceof Error ? error.message : String(error);
243
+ logger.error(`Error in dialogue generation: ${errorMessage}`);
244
+ return {
245
+ content: [
246
+ {
247
+ type: 'text',
248
+ text: `Error generating dialogue: ${errorMessage}`,
249
+ },
250
+ ],
251
+ isError: true,
252
+ };
253
+ }
254
+ });
255
+ // List available voices
256
+ server.tool('gemini-list-voices', {}, async () => {
257
+ const voiceDescriptions = {
258
+ Zephyr: 'Bright',
259
+ Puck: 'Upbeat',
260
+ Charon: 'Informative',
261
+ Kore: 'Firm',
262
+ Fenrir: 'Excitable',
263
+ Leda: 'Youthful',
264
+ Orus: 'Firm',
265
+ Aoede: 'Breezy',
266
+ Callirrhoe: 'Easy-going',
267
+ Autonoe: 'Bright',
268
+ Enceladus: 'Breathy',
269
+ Iapetus: 'Clear',
270
+ Umbriel: 'Easy-going',
271
+ Algieba: 'Smooth',
272
+ Despina: 'Smooth',
273
+ Erinome: 'Clear',
274
+ Algenib: 'Gravelly',
275
+ Rasalgethi: 'Informative',
276
+ Laomedeia: 'Upbeat',
277
+ Achernar: 'Soft',
278
+ Alnilam: 'Firm',
279
+ Schedar: 'Even',
280
+ Gacrux: 'Mature',
281
+ Pulcherrima: 'Forward',
282
+ Achird: 'Friendly',
283
+ Zubenelgenubi: 'Casual',
284
+ Vindemiatrix: 'Gentle',
285
+ Sadachbia: 'Lively',
286
+ Sadaltager: 'Knowledgeable',
287
+ Sulafat: 'Warm',
288
+ };
289
+ let text = '**Available Voices for Speech Generation:**\n\n';
290
+ for (const voice of VOICES) {
291
+ text += `- **${voice}** - ${voiceDescriptions[voice] || ''}\n`;
292
+ }
293
+ text +=
294
+ '\n*Use these voices with gemini-speak or gemini-dialogue tools.*';
295
+ return {
296
+ content: [
297
+ {
298
+ type: 'text',
299
+ text,
300
+ },
301
+ ],
302
+ };
303
+ });
304
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Structured Output Tool - Get JSON responses with schema validation
3
+ *
4
+ * This tool enables Gemini to generate responses that adhere to a provided JSON Schema.
5
+ * Useful for:
6
+ * - Data extraction from unstructured text
7
+ * - Structured classification
8
+ * - Generating data for APIs or databases
9
+ *
10
+ * Supports combining with Google Search for grounded structured data.
11
+ */
12
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
13
+ /**
14
+ * Register structured output tools with the MCP server
15
+ */
16
+ export declare function registerStructuredTool(server: McpServer): void;
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Structured Output Tool - Get JSON responses with schema validation
3
+ *
4
+ * This tool enables Gemini to generate responses that adhere to a provided JSON Schema.
5
+ * Useful for:
6
+ * - Data extraction from unstructured text
7
+ * - Structured classification
8
+ * - Generating data for APIs or databases
9
+ *
10
+ * Supports combining with Google Search for grounded structured data.
11
+ */
12
+ import { z } from 'zod';
13
+ import { GoogleGenAI } from '@google/genai';
14
+ import { logger } from '../utils/logger.js';
15
+ /**
16
+ * Register structured output tools with the MCP server
17
+ */
18
+ export function registerStructuredTool(server) {
19
+ server.tool('gemini-structured', {
20
+ prompt: z
21
+ .string()
22
+ .describe('The prompt or data to process'),
23
+ schema: z
24
+ .string()
25
+ .describe('JSON Schema as a string. Example: {"type":"object","properties":{"name":{"type":"string"},"age":{"type":"integer"}},"required":["name"]}'),
26
+ useGoogleSearch: z
27
+ .boolean()
28
+ .default(false)
29
+ .describe('Use Google Search to ground the response in real-world data'),
30
+ }, async ({ prompt, schema, useGoogleSearch }) => {
31
+ logger.info(`Structured output request: ${prompt.substring(0, 50)}...`);
32
+ try {
33
+ const apiKey = process.env.GEMINI_API_KEY;
34
+ if (!apiKey) {
35
+ throw new Error('GEMINI_API_KEY not set');
36
+ }
37
+ // Parse the schema
38
+ let jsonSchema;
39
+ try {
40
+ jsonSchema = JSON.parse(schema);
41
+ }
42
+ catch {
43
+ throw new Error('Invalid JSON schema provided. Please provide a valid JSON Schema string.');
44
+ }
45
+ const genAI = new GoogleGenAI({ apiKey });
46
+ const model = process.env.GEMINI_PRO_MODEL || 'gemini-3-pro-preview';
47
+ // Build config
48
+ const config = {
49
+ responseMimeType: 'application/json',
50
+ responseJsonSchema: jsonSchema,
51
+ };
52
+ // Add Google Search if requested
53
+ if (useGoogleSearch) {
54
+ config.tools = [{ googleSearch: {} }];
55
+ }
56
+ // Execute
57
+ const response = await genAI.models.generateContent({
58
+ model,
59
+ contents: prompt,
60
+ config,
61
+ });
62
+ const responseText = response.text || '';
63
+ // Validate that we got valid JSON
64
+ let parsedResponse;
65
+ try {
66
+ parsedResponse = JSON.parse(responseText);
67
+ }
68
+ catch {
69
+ throw new Error('Response was not valid JSON');
70
+ }
71
+ // Format nicely for display
72
+ const formattedJson = JSON.stringify(parsedResponse, null, 2);
73
+ logger.info('Structured output completed successfully');
74
+ return {
75
+ content: [
76
+ {
77
+ type: 'text',
78
+ text: `**Structured Response:**\n\`\`\`json\n${formattedJson}\n\`\`\``,
79
+ },
80
+ ],
81
+ };
82
+ }
83
+ catch (error) {
84
+ const errorMessage = error instanceof Error ? error.message : String(error);
85
+ logger.error(`Error in structured output: ${errorMessage}`);
86
+ return {
87
+ content: [
88
+ {
89
+ type: 'text',
90
+ text: `Error generating structured output: ${errorMessage}`,
91
+ },
92
+ ],
93
+ isError: true,
94
+ };
95
+ }
96
+ });
97
+ // Convenience tool for common extraction patterns
98
+ server.tool('gemini-extract', {
99
+ text: z.string().describe('The text to extract information from'),
100
+ extractType: z
101
+ .enum(['entities', 'facts', 'summary', 'keywords', 'sentiment', 'custom'])
102
+ .describe('What type of information to extract'),
103
+ customFields: z
104
+ .string()
105
+ .optional()
106
+ .describe('For custom extraction: comma-separated list of fields to extract. Example: "name, date, amount, description"'),
107
+ }, async ({ text, extractType, customFields }) => {
108
+ logger.info(`Extraction request: ${extractType}`);
109
+ try {
110
+ const apiKey = process.env.GEMINI_API_KEY;
111
+ if (!apiKey) {
112
+ throw new Error('GEMINI_API_KEY not set');
113
+ }
114
+ const genAI = new GoogleGenAI({ apiKey });
115
+ const model = process.env.GEMINI_FLASH_MODEL || 'gemini-3-flash-preview';
116
+ // Build schema based on extraction type
117
+ let schema;
118
+ let prompt;
119
+ switch (extractType) {
120
+ case 'entities':
121
+ schema = {
122
+ type: 'object',
123
+ properties: {
124
+ people: { type: 'array', items: { type: 'string' }, description: 'Names of people mentioned' },
125
+ organizations: { type: 'array', items: { type: 'string' }, description: 'Organizations mentioned' },
126
+ locations: { type: 'array', items: { type: 'string' }, description: 'Locations mentioned' },
127
+ dates: { type: 'array', items: { type: 'string' }, description: 'Dates mentioned' },
128
+ amounts: { type: 'array', items: { type: 'string' }, description: 'Monetary amounts or quantities' },
129
+ },
130
+ required: ['people', 'organizations', 'locations', 'dates', 'amounts'],
131
+ };
132
+ prompt = `Extract all named entities from the following text:\n\n${text}`;
133
+ break;
134
+ case 'facts':
135
+ schema = {
136
+ type: 'object',
137
+ properties: {
138
+ facts: {
139
+ type: 'array',
140
+ items: {
141
+ type: 'object',
142
+ properties: {
143
+ fact: { type: 'string', description: 'A factual statement' },
144
+ confidence: { type: 'string', enum: ['high', 'medium', 'low'] },
145
+ },
146
+ required: ['fact', 'confidence'],
147
+ },
148
+ },
149
+ },
150
+ required: ['facts'],
151
+ };
152
+ prompt = `Extract all factual statements from the following text:\n\n${text}`;
153
+ break;
154
+ case 'summary':
155
+ schema = {
156
+ type: 'object',
157
+ properties: {
158
+ title: { type: 'string', description: 'A brief title' },
159
+ summary: { type: 'string', description: 'A concise summary' },
160
+ keyPoints: { type: 'array', items: { type: 'string' }, description: 'Key points' },
161
+ topics: { type: 'array', items: { type: 'string' }, description: 'Main topics covered' },
162
+ },
163
+ required: ['title', 'summary', 'keyPoints', 'topics'],
164
+ };
165
+ prompt = `Summarize the following text and extract key information:\n\n${text}`;
166
+ break;
167
+ case 'keywords':
168
+ schema = {
169
+ type: 'object',
170
+ properties: {
171
+ keywords: { type: 'array', items: { type: 'string' }, description: 'Important keywords' },
172
+ phrases: { type: 'array', items: { type: 'string' }, description: 'Important phrases' },
173
+ categories: { type: 'array', items: { type: 'string' }, description: 'Topic categories' },
174
+ },
175
+ required: ['keywords', 'phrases', 'categories'],
176
+ };
177
+ prompt = `Extract keywords, important phrases, and topic categories from the following text:\n\n${text}`;
178
+ break;
179
+ case 'sentiment':
180
+ schema = {
181
+ type: 'object',
182
+ properties: {
183
+ overallSentiment: { type: 'string', enum: ['positive', 'negative', 'neutral', 'mixed'] },
184
+ sentimentScore: { type: 'number', description: 'Score from -1 (negative) to 1 (positive)' },
185
+ emotions: { type: 'array', items: { type: 'string' }, description: 'Emotions detected' },
186
+ reasoning: { type: 'string', description: 'Brief explanation of the sentiment analysis' },
187
+ },
188
+ required: ['overallSentiment', 'sentimentScore', 'emotions', 'reasoning'],
189
+ };
190
+ prompt = `Analyze the sentiment of the following text:\n\n${text}`;
191
+ break;
192
+ case 'custom':
193
+ if (!customFields) {
194
+ throw new Error('customFields is required for custom extraction');
195
+ }
196
+ const fields = customFields.split(',').map((f) => f.trim());
197
+ const properties = {};
198
+ for (const field of fields) {
199
+ properties[field] = { type: 'string', description: `The ${field} extracted from the text` };
200
+ }
201
+ schema = {
202
+ type: 'object',
203
+ properties,
204
+ required: fields,
205
+ };
206
+ prompt = `Extract the following information from the text: ${customFields}\n\nText:\n${text}`;
207
+ break;
208
+ default:
209
+ throw new Error(`Unknown extraction type: ${extractType}`);
210
+ }
211
+ // Execute
212
+ const response = await genAI.models.generateContent({
213
+ model,
214
+ contents: prompt,
215
+ config: {
216
+ responseMimeType: 'application/json',
217
+ responseJsonSchema: schema,
218
+ },
219
+ });
220
+ const responseText = response.text || '';
221
+ const parsedResponse = JSON.parse(responseText);
222
+ const formattedJson = JSON.stringify(parsedResponse, null, 2);
223
+ logger.info('Extraction completed successfully');
224
+ return {
225
+ content: [
226
+ {
227
+ type: 'text',
228
+ text: `**Extracted ${extractType}:**\n\`\`\`json\n${formattedJson}\n\`\`\``,
229
+ },
230
+ ],
231
+ };
232
+ }
233
+ catch (error) {
234
+ const errorMessage = error instanceof Error ? error.message : String(error);
235
+ logger.error(`Error in extraction: ${errorMessage}`);
236
+ return {
237
+ content: [
238
+ {
239
+ type: 'text',
240
+ text: `Error extracting data: ${errorMessage}`,
241
+ },
242
+ ],
243
+ isError: true,
244
+ };
245
+ }
246
+ });
247
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Summarize Tool - Provides content summarization using Gemini models
3
+ *
4
+ * This tool allows summarizing long text content at different levels of detail.
5
+ */
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ /**
8
+ * Register summarization tools with the MCP server
9
+ */
10
+ export declare function registerSummarizeTool(server: McpServer): void;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Summarize Tool - Provides content summarization using Gemini models
3
+ *
4
+ * This tool allows summarizing long text content at different levels of detail.
5
+ */
6
+ import { z } from "zod";
7
+ import { generateWithGeminiFlash } from "../gemini-client.js";
8
+ /**
9
+ * Register summarization tools with the MCP server
10
+ */
11
+ export function registerSummarizeTool(server) {
12
+ server.tool("gemini-summarize", {
13
+ content: z.string().describe("The content to summarize"),
14
+ length: z.enum(["brief", "moderate", "detailed"]).default("moderate").describe("The desired summary length"),
15
+ format: z.enum(["paragraph", "bullet-points", "outline"]).default("paragraph").describe("The output format")
16
+ }, async ({ content, length, format }) => {
17
+ console.log(`Summarizing content (${length}, ${format})`);
18
+ try {
19
+ // Configure length parameters
20
+ let wordCount;
21
+ switch (length) {
22
+ case "brief":
23
+ wordCount = "50-100 words";
24
+ break;
25
+ case "moderate":
26
+ wordCount = "150-250 words";
27
+ break;
28
+ case "detailed":
29
+ wordCount = "300-500 words";
30
+ break;
31
+ default:
32
+ wordCount = "150-250 words";
33
+ }
34
+ // Configure format instructions
35
+ let formatInstructions;
36
+ switch (format) {
37
+ case "bullet-points":
38
+ formatInstructions = "Use bullet points for each key idea or concept.";
39
+ break;
40
+ case "outline":
41
+ formatInstructions = "Create a structured outline with main points and sub-points.";
42
+ break;
43
+ case "paragraph":
44
+ default:
45
+ formatInstructions = "Use cohesive paragraphs with clear transitions.";
46
+ }
47
+ const prompt = `
48
+ Summarize the following content in ${wordCount}. ${formatInstructions}
49
+
50
+ Focus on capturing the most important information, main arguments, and key conclusions.
51
+
52
+ Content to summarize:
53
+ """
54
+ ${content}
55
+ """
56
+ `;
57
+ const response = await generateWithGeminiFlash(prompt);
58
+ return {
59
+ content: [{
60
+ type: "text",
61
+ text: response
62
+ }]
63
+ };
64
+ }
65
+ catch (error) {
66
+ const errorMessage = error instanceof Error ? error.message : String(error);
67
+ console.error(`Error summarizing content: ${errorMessage}`);
68
+ return {
69
+ content: [{
70
+ type: "text",
71
+ text: `Error: ${errorMessage}`
72
+ }],
73
+ isError: true
74
+ };
75
+ }
76
+ });
77
+ }