converteverything-mcp 1.2.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.
package/dist/index.js ADDED
@@ -0,0 +1,1063 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * ConvertEverything MCP Server
5
+ *
6
+ * An MCP server that enables AI assistants to convert files between 93+ formats
7
+ * using the ConvertEverything.io API.
8
+ *
9
+ * Security: This server only uses the public API with user-provided API keys.
10
+ * No internal endpoints, secrets, or admin functionality is exposed.
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
47
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
48
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
49
+ const zod_1 = require("zod");
50
+ const fs = __importStar(require("fs"));
51
+ const path = __importStar(require("path"));
52
+ const client_js_1 = require("./client.js");
53
+ const types_js_2 = require("./types.js");
54
+ // ============================================================================
55
+ // Package Info
56
+ // ============================================================================
57
+ const PACKAGE_VERSION = "1.2.0";
58
+ const PACKAGE_NAME = "converteverything-mcp";
59
+ function parseArgs() {
60
+ const args = process.argv.slice(2);
61
+ const result = {};
62
+ for (let i = 0; i < args.length; i++) {
63
+ const arg = args[i];
64
+ const nextArg = args[i + 1];
65
+ if (arg === "--help" || arg === "-h") {
66
+ result.help = true;
67
+ }
68
+ else if (arg === "--version" || arg === "-v") {
69
+ result.version = true;
70
+ }
71
+ else if ((arg === "--api-key" || arg === "-k") && nextArg) {
72
+ result.apiKey = nextArg;
73
+ i++;
74
+ }
75
+ else if ((arg === "--base-url" || arg === "-u") && nextArg) {
76
+ result.baseUrl = nextArg;
77
+ i++;
78
+ }
79
+ else if (arg.startsWith("--api-key=")) {
80
+ result.apiKey = arg.split("=")[1];
81
+ }
82
+ else if (arg.startsWith("--base-url=")) {
83
+ result.baseUrl = arg.split("=")[1];
84
+ }
85
+ }
86
+ return result;
87
+ }
88
+ function showHelp() {
89
+ console.log(`
90
+ ${PACKAGE_NAME} v${PACKAGE_VERSION}
91
+
92
+ MCP server for ConvertEverything.io - Convert files between 93+ formats
93
+
94
+ USAGE:
95
+ npx ${PACKAGE_NAME} [OPTIONS]
96
+
97
+ OPTIONS:
98
+ -k, --api-key <key> ConvertEverything.io API key (required)
99
+ -u, --base-url <url> Custom API URL (default: https://converteverything.io)
100
+ -h, --help Show this help message
101
+ -v, --version Show version number
102
+
103
+ ENVIRONMENT VARIABLES:
104
+ CONVERTEVERYTHING_API_KEY API key (alternative to --api-key)
105
+ CONVERTEVERYTHING_BASE_URL Base URL (alternative to --base-url)
106
+
107
+ EXAMPLES:
108
+ npx ${PACKAGE_NAME} --api-key ce_your_key_here
109
+ npx ${PACKAGE_NAME} -k ce_your_key_here
110
+
111
+ GET AN API KEY:
112
+ 1. Sign up at https://converteverything.io/register
113
+ 2. Subscribe to Silver ($9.99/mo) or Gold ($19.99/mo)
114
+ 3. Create a key at https://converteverything.io/api-keys
115
+
116
+ MORE INFO:
117
+ https://github.com/converteverything/converteverything-mcp
118
+ `);
119
+ }
120
+ function showVersion() {
121
+ console.log(`${PACKAGE_NAME} v${PACKAGE_VERSION}`);
122
+ }
123
+ const cliArgs = parseArgs();
124
+ // Handle --help and --version before anything else
125
+ if (cliArgs.help) {
126
+ showHelp();
127
+ process.exit(0);
128
+ }
129
+ if (cliArgs.version) {
130
+ showVersion();
131
+ process.exit(0);
132
+ }
133
+ // ============================================================================
134
+ // Configuration (CLI args take precedence over env vars)
135
+ // ============================================================================
136
+ const API_KEY = cliArgs.apiKey || process.env.CONVERTEVERYTHING_API_KEY;
137
+ const BASE_URL = cliArgs.baseUrl || process.env.CONVERTEVERYTHING_BASE_URL || types_js_2.DEFAULT_BASE_URL;
138
+ // ============================================================================
139
+ // Input Validation Schemas
140
+ // ============================================================================
141
+ const ConvertFileSchema = zod_1.z.object({
142
+ file_path: zod_1.z.string().min(1).describe("Path to the file to convert"),
143
+ target_format: zod_1.z.string().min(1).max(10).describe("Target format (e.g., 'mp3', 'pdf', 'png')"),
144
+ options: zod_1.z.record(zod_1.z.unknown()).optional().describe("Conversion settings"),
145
+ preset: zod_1.z.string().optional().describe("Preset name: web-optimized, high-quality, smallest-size, balanced, print-ready, archive"),
146
+ });
147
+ const ConvertBase64Schema = zod_1.z.object({
148
+ data: zod_1.z.string().min(1).describe("Base64-encoded file data"),
149
+ filename: zod_1.z.string().min(1).max(255).describe("Original filename with extension"),
150
+ target_format: zod_1.z.string().min(1).max(10).describe("Target format"),
151
+ options: zod_1.z.record(zod_1.z.unknown()).optional().describe("Conversion settings"),
152
+ preset: zod_1.z.string().optional().describe("Preset name"),
153
+ });
154
+ const BatchConvertSchema = zod_1.z.object({
155
+ files: zod_1.z.array(zod_1.z.object({
156
+ file_path: zod_1.z.string().min(1),
157
+ target_format: zod_1.z.string().min(1).max(10),
158
+ })).min(1).max(50).describe("Array of files to convert"),
159
+ options: zod_1.z.record(zod_1.z.unknown()).optional().describe("Shared conversion settings"),
160
+ preset: zod_1.z.string().optional().describe("Preset name to apply to all files"),
161
+ });
162
+ const GetConversionStatusSchema = zod_1.z.object({
163
+ conversion_id: zod_1.z.string().uuid().describe("The conversion ID"),
164
+ });
165
+ const WaitForConversionSchema = zod_1.z.object({
166
+ conversion_id: zod_1.z.string().uuid().describe("The conversion ID"),
167
+ timeout: zod_1.z.number().optional().describe("Max wait time in seconds (default: 300)"),
168
+ poll_interval: zod_1.z.number().optional().describe("Poll interval in seconds (default: 2)"),
169
+ });
170
+ const DownloadFileSchema = zod_1.z.object({
171
+ conversion_id: zod_1.z.string().uuid().describe("The conversion ID"),
172
+ save_path: zod_1.z.string().optional().describe("Optional path to save the file"),
173
+ });
174
+ const ListConversionsSchema = zod_1.z.object({
175
+ page: zod_1.z.number().optional().describe("Page number (default: 1)"),
176
+ per_page: zod_1.z.number().optional().describe("Items per page (default: 10, max: 100)"),
177
+ });
178
+ const CancelConversionSchema = zod_1.z.object({
179
+ conversion_id: zod_1.z.string().uuid().describe("The conversion ID to cancel"),
180
+ });
181
+ const GetFileInfoSchema = zod_1.z.object({
182
+ file_path: zod_1.z.string().min(1).describe("Path to the file to analyze"),
183
+ });
184
+ const EstimateOutputSizeSchema = zod_1.z.object({
185
+ file_path: zod_1.z.string().min(1).describe("Path to the source file"),
186
+ target_format: zod_1.z.string().min(1).max(10).describe("Target format"),
187
+ options: zod_1.z.record(zod_1.z.unknown()).optional().describe("Conversion options"),
188
+ preset: zod_1.z.string().optional().describe("Preset name"),
189
+ });
190
+ // ============================================================================
191
+ // Tool Definitions
192
+ // ============================================================================
193
+ const TOOLS = [
194
+ {
195
+ name: "get_supported_formats",
196
+ description: "Get a list of all supported file formats for conversion, organized by category " +
197
+ "(audio, video, image, document, ebook, data, 3d, font, archive, cad).",
198
+ inputSchema: {
199
+ type: "object",
200
+ properties: {},
201
+ required: [],
202
+ },
203
+ },
204
+ {
205
+ name: "get_usage",
206
+ description: "Get your current API usage statistics and limits. Shows daily conversions used, " +
207
+ "daily limit, maximum file size for your tier, and your subscription tier.",
208
+ inputSchema: {
209
+ type: "object",
210
+ properties: {},
211
+ required: [],
212
+ },
213
+ },
214
+ {
215
+ name: "list_presets",
216
+ description: "List available conversion presets with their settings. " +
217
+ "Presets: web-optimized, high-quality, smallest-size, balanced, print-ready, archive.",
218
+ inputSchema: {
219
+ type: "object",
220
+ properties: {},
221
+ required: [],
222
+ },
223
+ },
224
+ {
225
+ name: "convert_file",
226
+ description: "Convert a file from one format to another. Supports 93+ formats. " +
227
+ "Use 'preset' for quick settings or 'options' for fine-grained control.",
228
+ inputSchema: {
229
+ type: "object",
230
+ properties: {
231
+ file_path: { type: "string", description: "Path to the file to convert" },
232
+ target_format: { type: "string", description: "Target format (e.g., 'mp3', 'pdf')" },
233
+ options: { type: "object", description: "Conversion settings (bitrate, quality, etc.)" },
234
+ preset: { type: "string", description: "Preset: web-optimized, high-quality, smallest-size, balanced, print-ready, archive" },
235
+ },
236
+ required: ["file_path", "target_format"],
237
+ },
238
+ },
239
+ {
240
+ name: "convert_base64",
241
+ description: "Convert a file provided as base64-encoded data. Useful for in-memory files.",
242
+ inputSchema: {
243
+ type: "object",
244
+ properties: {
245
+ data: { type: "string", description: "Base64-encoded file data" },
246
+ filename: { type: "string", description: "Original filename with extension" },
247
+ target_format: { type: "string", description: "Target format" },
248
+ options: { type: "object", description: "Conversion settings" },
249
+ preset: { type: "string", description: "Preset name" },
250
+ },
251
+ required: ["data", "filename", "target_format"],
252
+ },
253
+ },
254
+ {
255
+ name: "batch_convert",
256
+ description: "Convert multiple files at once. Provide an array of file paths and target formats. " +
257
+ "Returns conversion IDs for each file.",
258
+ inputSchema: {
259
+ type: "object",
260
+ properties: {
261
+ files: {
262
+ type: "array",
263
+ items: {
264
+ type: "object",
265
+ properties: {
266
+ file_path: { type: "string" },
267
+ target_format: { type: "string" },
268
+ },
269
+ required: ["file_path", "target_format"],
270
+ },
271
+ description: "Array of {file_path, target_format} objects",
272
+ },
273
+ options: { type: "object", description: "Shared conversion settings" },
274
+ preset: { type: "string", description: "Preset to apply to all files" },
275
+ },
276
+ required: ["files"],
277
+ },
278
+ },
279
+ {
280
+ name: "get_conversion_status",
281
+ description: "Check the status of a conversion. Returns: pending, processing, completed, or failed.",
282
+ inputSchema: {
283
+ type: "object",
284
+ properties: {
285
+ conversion_id: { type: "string", description: "The conversion ID" },
286
+ },
287
+ required: ["conversion_id"],
288
+ },
289
+ },
290
+ {
291
+ name: "wait_for_conversion",
292
+ description: "Wait for a conversion to complete, polling until done. " +
293
+ "Returns the final status when complete or failed.",
294
+ inputSchema: {
295
+ type: "object",
296
+ properties: {
297
+ conversion_id: { type: "string", description: "The conversion ID" },
298
+ timeout: { type: "number", description: "Max wait time in seconds (default: 300)" },
299
+ poll_interval: { type: "number", description: "Poll interval in seconds (default: 2)" },
300
+ },
301
+ required: ["conversion_id"],
302
+ },
303
+ },
304
+ {
305
+ name: "download_file",
306
+ description: "Download a completed conversion. Returns base64 data or saves to disk.",
307
+ inputSchema: {
308
+ type: "object",
309
+ properties: {
310
+ conversion_id: { type: "string", description: "The conversion ID" },
311
+ save_path: { type: "string", description: "Optional path to save the file" },
312
+ },
313
+ required: ["conversion_id"],
314
+ },
315
+ },
316
+ {
317
+ name: "list_conversions",
318
+ description: "List your recent conversions with pagination.",
319
+ inputSchema: {
320
+ type: "object",
321
+ properties: {
322
+ page: { type: "number", description: "Page number (default: 1)" },
323
+ per_page: { type: "number", description: "Items per page (default: 10, max: 100)" },
324
+ },
325
+ required: [],
326
+ },
327
+ },
328
+ {
329
+ name: "cancel_conversion",
330
+ description: "Cancel an in-progress conversion. Cannot cancel completed or failed conversions.",
331
+ inputSchema: {
332
+ type: "object",
333
+ properties: {
334
+ conversion_id: { type: "string", description: "The conversion ID to cancel" },
335
+ },
336
+ required: ["conversion_id"],
337
+ },
338
+ },
339
+ {
340
+ name: "get_file_info",
341
+ description: "Get file information including size, format, and MIME type before conversion.",
342
+ inputSchema: {
343
+ type: "object",
344
+ properties: {
345
+ file_path: { type: "string", description: "Path to the file to analyze" },
346
+ },
347
+ required: ["file_path"],
348
+ },
349
+ },
350
+ {
351
+ name: "estimate_output_size",
352
+ description: "Estimate the output file size after conversion based on format and options.",
353
+ inputSchema: {
354
+ type: "object",
355
+ properties: {
356
+ file_path: { type: "string", description: "Path to the source file" },
357
+ target_format: { type: "string", description: "Target format" },
358
+ options: { type: "object", description: "Conversion options" },
359
+ preset: { type: "string", description: "Preset name" },
360
+ },
361
+ required: ["file_path", "target_format"],
362
+ },
363
+ },
364
+ ];
365
+ // ============================================================================
366
+ // Resource Definitions
367
+ // ============================================================================
368
+ const RESOURCES = [
369
+ {
370
+ uri: "converteverything://formats",
371
+ name: "Supported Formats",
372
+ description: "Complete list of all 93+ supported file formats by category",
373
+ mimeType: "text/plain",
374
+ },
375
+ {
376
+ uri: "converteverything://formats/audio",
377
+ name: "Audio Formats",
378
+ description: "Supported audio formats: mp3, wav, flac, aac, ogg, m4a, etc.",
379
+ mimeType: "text/plain",
380
+ },
381
+ {
382
+ uri: "converteverything://formats/video",
383
+ name: "Video Formats",
384
+ description: "Supported video formats: mp4, avi, mkv, mov, webm, etc.",
385
+ mimeType: "text/plain",
386
+ },
387
+ {
388
+ uri: "converteverything://formats/image",
389
+ name: "Image Formats",
390
+ description: "Supported image formats: jpg, png, gif, webp, svg, etc.",
391
+ mimeType: "text/plain",
392
+ },
393
+ {
394
+ uri: "converteverything://formats/document",
395
+ name: "Document Formats",
396
+ description: "Supported document formats: pdf, docx, xlsx, pptx, etc.",
397
+ mimeType: "text/plain",
398
+ },
399
+ {
400
+ uri: "converteverything://presets",
401
+ name: "Conversion Presets",
402
+ description: "Available presets: web-optimized, high-quality, smallest-size, etc.",
403
+ mimeType: "text/plain",
404
+ },
405
+ {
406
+ uri: "converteverything://subscription",
407
+ name: "Subscription Info",
408
+ description: "Your current subscription tier, limits, and usage",
409
+ mimeType: "text/plain",
410
+ },
411
+ ];
412
+ // Format categories for resources (derived from types.ts)
413
+ const FORMAT_CATEGORY_KEYS = [
414
+ "audio", "video", "image", "document", "ebook", "data", "3d", "font", "archive", "cad"
415
+ ];
416
+ // ============================================================================
417
+ // MCP Prompts
418
+ // ============================================================================
419
+ const PROMPTS = [
420
+ {
421
+ name: "convert-for-web",
422
+ description: "Convert files to web-optimized formats",
423
+ arguments: [
424
+ {
425
+ name: "file_path",
426
+ description: "Path to the file or folder to convert",
427
+ required: true,
428
+ },
429
+ ],
430
+ },
431
+ {
432
+ name: "batch-convert-folder",
433
+ description: "Convert all files in a folder to a target format",
434
+ arguments: [
435
+ {
436
+ name: "folder_path",
437
+ description: "Path to the folder containing files",
438
+ required: true,
439
+ },
440
+ {
441
+ name: "target_format",
442
+ description: "Target format for all files",
443
+ required: true,
444
+ },
445
+ ],
446
+ },
447
+ {
448
+ name: "optimize-images",
449
+ description: "Optimize images for web or print",
450
+ arguments: [
451
+ {
452
+ name: "file_path",
453
+ description: "Path to the image or folder of images",
454
+ required: true,
455
+ },
456
+ {
457
+ name: "purpose",
458
+ description: "Purpose: 'web' for smaller files, 'print' for high quality",
459
+ required: false,
460
+ },
461
+ ],
462
+ },
463
+ {
464
+ name: "convert-video-for-streaming",
465
+ description: "Convert video to streaming-friendly format (MP4/WebM)",
466
+ arguments: [
467
+ {
468
+ name: "file_path",
469
+ description: "Path to the video file",
470
+ required: true,
471
+ },
472
+ {
473
+ name: "quality",
474
+ description: "Quality level: 'low', 'medium', 'high'",
475
+ required: false,
476
+ },
477
+ ],
478
+ },
479
+ {
480
+ name: "document-to-pdf",
481
+ description: "Convert documents to PDF format",
482
+ arguments: [
483
+ {
484
+ name: "file_path",
485
+ description: "Path to the document file",
486
+ required: true,
487
+ },
488
+ {
489
+ name: "quality",
490
+ description: "PDF quality: 'screen', 'ebook', 'printer', 'prepress'",
491
+ required: false,
492
+ },
493
+ ],
494
+ },
495
+ ];
496
+ // ============================================================================
497
+ // Server Implementation
498
+ // ============================================================================
499
+ class ConvertEverythingServer {
500
+ server;
501
+ client = null;
502
+ constructor() {
503
+ this.server = new index_js_1.Server({
504
+ name: PACKAGE_NAME,
505
+ version: PACKAGE_VERSION,
506
+ }, {
507
+ capabilities: {
508
+ tools: {},
509
+ resources: {},
510
+ prompts: {},
511
+ },
512
+ });
513
+ this.setupHandlers();
514
+ }
515
+ getClient() {
516
+ if (!this.client) {
517
+ if (!API_KEY) {
518
+ throw new Error("API key required. Use --api-key or set CONVERTEVERYTHING_API_KEY. " +
519
+ "Get your key at https://converteverything.io/api-keys");
520
+ }
521
+ this.client = new client_js_1.ConvertEverythingClient({
522
+ apiKey: API_KEY,
523
+ baseUrl: BASE_URL,
524
+ });
525
+ }
526
+ return this.client;
527
+ }
528
+ setupHandlers() {
529
+ // List available tools
530
+ this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
531
+ tools: TOOLS,
532
+ }));
533
+ // List available resources
534
+ this.server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => ({
535
+ resources: RESOURCES,
536
+ }));
537
+ // Read resource content
538
+ this.server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
539
+ const uri = request.params.uri;
540
+ return this.handleReadResource(uri);
541
+ });
542
+ // List available prompts
543
+ this.server.setRequestHandler(types_js_1.ListPromptsRequestSchema, async () => ({
544
+ prompts: PROMPTS,
545
+ }));
546
+ // Get prompt content
547
+ this.server.setRequestHandler(types_js_1.GetPromptRequestSchema, async (request) => {
548
+ const { name, arguments: promptArgs } = request.params;
549
+ return this.handleGetPrompt(name, promptArgs || {});
550
+ });
551
+ // Handle tool calls
552
+ this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
553
+ const { name, arguments: args } = request.params;
554
+ try {
555
+ switch (name) {
556
+ case "get_supported_formats":
557
+ return await this.handleGetSupportedFormats();
558
+ case "get_usage":
559
+ return await this.handleGetUsage();
560
+ case "list_presets":
561
+ return this.handleListPresets();
562
+ case "convert_file":
563
+ return await this.handleConvertFile(args);
564
+ case "convert_base64":
565
+ return await this.handleConvertBase64(args);
566
+ case "batch_convert":
567
+ return await this.handleBatchConvert(args);
568
+ case "get_conversion_status":
569
+ return await this.handleGetConversionStatus(args);
570
+ case "wait_for_conversion":
571
+ return await this.handleWaitForConversion(args);
572
+ case "download_file":
573
+ return await this.handleDownloadFile(args);
574
+ case "list_conversions":
575
+ return await this.handleListConversions(args);
576
+ case "cancel_conversion":
577
+ return await this.handleCancelConversion(args);
578
+ case "get_file_info":
579
+ return await this.handleGetFileInfo(args);
580
+ case "estimate_output_size":
581
+ return await this.handleEstimateOutputSize(args);
582
+ default:
583
+ throw new Error(`Unknown tool: ${name}`);
584
+ }
585
+ }
586
+ catch (error) {
587
+ const message = error instanceof Error ? error.message : String(error);
588
+ return {
589
+ content: [{ type: "text", text: `Error: ${message}` }],
590
+ isError: true,
591
+ };
592
+ }
593
+ });
594
+ }
595
+ handleReadResource(uri) {
596
+ let text = "";
597
+ if (uri === "converteverything://formats") {
598
+ text = "SUPPORTED FORMATS (93+ total)\n\n";
599
+ for (const category of FORMAT_CATEGORY_KEYS) {
600
+ const formats = (0, types_js_2.getFormatsByCategory)(category);
601
+ const description = types_js_2.FORMAT_CATEGORY_DESCRIPTIONS[category];
602
+ text += `${category.toUpperCase()}: ${formats.join(", ")}\n`;
603
+ text += ` ${description}\n\n`;
604
+ }
605
+ }
606
+ else if (uri === "converteverything://presets") {
607
+ text = "CONVERSION PRESETS\n\n";
608
+ for (const [name, config] of Object.entries(types_js_2.PRESET_CONFIGS)) {
609
+ text += `${name}: ${config.description}\n`;
610
+ for (const [category, options] of Object.entries(config.options)) {
611
+ text += ` ${category}: ${JSON.stringify(options)}\n`;
612
+ }
613
+ text += "\n";
614
+ }
615
+ }
616
+ else if (uri.startsWith("converteverything://formats/")) {
617
+ const category = uri.replace("converteverything://formats/", "");
618
+ if (types_js_2.FORMAT_CATEGORY_DESCRIPTIONS[category]) {
619
+ const formats = (0, types_js_2.getFormatsByCategory)(category);
620
+ const description = types_js_2.FORMAT_CATEGORY_DESCRIPTIONS[category];
621
+ text = `${category.toUpperCase()} FORMATS\n\n`;
622
+ text += `${description}\n\n`;
623
+ text += `Formats: ${formats.join(", ")}\n`;
624
+ }
625
+ else {
626
+ throw new Error(`Unknown format category: ${category}`);
627
+ }
628
+ }
629
+ else if (uri === "converteverything://subscription") {
630
+ // This is a dynamic resource - fetch usage info
631
+ return this.handleSubscriptionResource(uri);
632
+ }
633
+ else {
634
+ throw new Error(`Unknown resource: ${uri}`);
635
+ }
636
+ return {
637
+ contents: [{ uri, mimeType: "text/plain", text }],
638
+ };
639
+ }
640
+ async handleSubscriptionResource(uri) {
641
+ try {
642
+ const client = this.getClient();
643
+ const usage = await client.getUsage();
644
+ const limitText = usage.conversions_limit === -1
645
+ ? "Unlimited"
646
+ : usage.conversions_limit.toString();
647
+ const remainingText = usage.conversions_remaining === Infinity
648
+ ? "Unlimited"
649
+ : usage.conversions_remaining.toString();
650
+ const text = `SUBSCRIPTION INFO\n\n` +
651
+ `Tier: ${usage.tier.toUpperCase()}\n\n` +
652
+ `Daily Conversions:\n` +
653
+ ` Used: ${usage.conversions_used}\n` +
654
+ ` Limit: ${limitText}\n` +
655
+ ` Remaining: ${remainingText}\n\n` +
656
+ `Max File Size: ${usage.max_file_size_mb} MB\n` +
657
+ `File Retention: ${usage.file_retention_hours} hours\n\n` +
658
+ `Upgrade: https://converteverything.io/pricing`;
659
+ return {
660
+ contents: [{ uri, mimeType: "text/plain", text }],
661
+ };
662
+ }
663
+ catch (error) {
664
+ const message = error instanceof Error ? error.message : String(error);
665
+ return {
666
+ contents: [{ uri, mimeType: "text/plain", text: `Error fetching subscription info: ${message}` }],
667
+ };
668
+ }
669
+ }
670
+ async handleGetSupportedFormats() {
671
+ const client = this.getClient();
672
+ const formats = await client.getSupportedFormats();
673
+ let text = `Supported Formats (${formats.total_formats} total):\n\n`;
674
+ for (const [category, formatList] of Object.entries(formats.formats)) {
675
+ if (Array.isArray(formatList) && formatList.length > 0) {
676
+ text += `${category.toUpperCase()}: ${formatList.join(", ")}\n`;
677
+ }
678
+ }
679
+ return { content: [{ type: "text", text }] };
680
+ }
681
+ async handleGetUsage() {
682
+ const client = this.getClient();
683
+ const usage = await client.getUsage();
684
+ const limitText = usage.conversions_limit === -1
685
+ ? "Unlimited"
686
+ : usage.conversions_limit.toString();
687
+ const remainingText = usage.conversions_remaining === Infinity
688
+ ? "Unlimited"
689
+ : usage.conversions_remaining.toString();
690
+ const text = `API Usage:\n` +
691
+ ` Tier: ${usage.tier}\n` +
692
+ ` Daily conversions: ${usage.conversions_used} / ${limitText}\n` +
693
+ ` Remaining: ${remainingText}\n` +
694
+ ` Max file size: ${usage.max_file_size_mb} MB\n` +
695
+ ` File retention: ${usage.file_retention_hours} hours`;
696
+ return { content: [{ type: "text", text }] };
697
+ }
698
+ handleListPresets() {
699
+ let text = "Available Presets:\n\n";
700
+ for (const [name, config] of Object.entries(types_js_2.PRESET_CONFIGS)) {
701
+ text += `${name}:\n ${config.description}\n`;
702
+ for (const [category, options] of Object.entries(config.options)) {
703
+ text += ` ${category}: ${JSON.stringify(options)}\n`;
704
+ }
705
+ text += "\n";
706
+ }
707
+ return { content: [{ type: "text", text }] };
708
+ }
709
+ resolveOptions(targetFormat, preset, options) {
710
+ let resolvedOptions = options;
711
+ if (preset) {
712
+ const category = (0, types_js_2.getFormatCategory)(targetFormat);
713
+ if (category) {
714
+ const presetOptions = (0, types_js_2.getPresetOptions)(preset, category);
715
+ if (presetOptions) {
716
+ resolvedOptions = { ...presetOptions, ...options };
717
+ }
718
+ }
719
+ }
720
+ return resolvedOptions;
721
+ }
722
+ async handleConvertFile(args) {
723
+ const parsed = ConvertFileSchema.parse(args);
724
+ const client = this.getClient();
725
+ const options = this.resolveOptions(parsed.target_format, parsed.preset, parsed.options);
726
+ const result = await client.convertFile(parsed.file_path, parsed.target_format, options);
727
+ const text = `Conversion started:\n` +
728
+ ` ID: ${result.id}\n` +
729
+ ` Status: ${result.status}\n` +
730
+ ` From: ${result.source_format} → ${result.target_format}\n` +
731
+ ` File: ${result.original_filename}\n` +
732
+ (parsed.preset ? ` Preset: ${parsed.preset}\n` : "") +
733
+ `\nUse wait_for_conversion to wait for completion, or get_conversion_status to check progress.`;
734
+ return { content: [{ type: "text", text }] };
735
+ }
736
+ async handleConvertBase64(args) {
737
+ const parsed = ConvertBase64Schema.parse(args);
738
+ const client = this.getClient();
739
+ const options = this.resolveOptions(parsed.target_format, parsed.preset, parsed.options);
740
+ const result = await client.convertFileBuffer(parsed.data, parsed.filename, parsed.target_format, options);
741
+ const text = `Conversion started:\n` +
742
+ ` ID: ${result.id}\n` +
743
+ ` Status: ${result.status}\n` +
744
+ ` From: ${result.source_format} → ${result.target_format}\n` +
745
+ ` File: ${result.original_filename}\n` +
746
+ (parsed.preset ? ` Preset: ${parsed.preset}\n` : "");
747
+ return { content: [{ type: "text", text }] };
748
+ }
749
+ async handleBatchConvert(args) {
750
+ const parsed = BatchConvertSchema.parse(args);
751
+ const client = this.getClient();
752
+ const results = [];
753
+ for (const file of parsed.files) {
754
+ try {
755
+ const options = this.resolveOptions(file.target_format, parsed.preset, parsed.options);
756
+ const result = await client.convertFile(file.file_path, file.target_format, options);
757
+ results.push({ file: file.file_path, id: result.id });
758
+ }
759
+ catch (error) {
760
+ const message = error instanceof Error ? error.message : String(error);
761
+ results.push({ file: file.file_path, error: message });
762
+ }
763
+ }
764
+ const successful = results.filter((r) => r.id);
765
+ const failed = results.filter((r) => r.error);
766
+ let text = `Batch conversion started:\n`;
767
+ text += ` Total: ${results.length}\n`;
768
+ text += ` Started: ${successful.length}\n`;
769
+ text += ` Failed: ${failed.length}\n\n`;
770
+ if (successful.length > 0) {
771
+ text += "Conversions:\n";
772
+ for (const r of successful) {
773
+ text += ` ${path.basename(r.file)} → ${r.id}\n`;
774
+ }
775
+ }
776
+ if (failed.length > 0) {
777
+ text += "\nErrors:\n";
778
+ for (const r of failed) {
779
+ text += ` ${path.basename(r.file)}: ${r.error}\n`;
780
+ }
781
+ }
782
+ return { content: [{ type: "text", text }] };
783
+ }
784
+ async handleGetConversionStatus(args) {
785
+ const parsed = GetConversionStatusSchema.parse(args);
786
+ const client = this.getClient();
787
+ const result = await client.getConversionStatus(parsed.conversion_id);
788
+ let text = `Conversion Status:\n` +
789
+ ` ID: ${result.id}\n` +
790
+ ` Status: ${result.status}\n` +
791
+ ` From: ${result.source_format} → ${result.target_format}\n` +
792
+ ` File: ${result.original_filename}\n`;
793
+ if (result.created_at)
794
+ text += ` Created: ${result.created_at}\n`;
795
+ if (result.started_at)
796
+ text += ` Started: ${result.started_at}\n`;
797
+ if (result.completed_at)
798
+ text += ` Completed: ${result.completed_at}\n`;
799
+ if (result.error_message)
800
+ text += ` Error: ${result.error_message}\n`;
801
+ if (result.download_url) {
802
+ text += `\nFile is ready for download. Use download_file to get the converted file.`;
803
+ if (result.download_expires_at) {
804
+ text += `\nDownload expires: ${result.download_expires_at}`;
805
+ }
806
+ }
807
+ return { content: [{ type: "text", text }] };
808
+ }
809
+ async handleWaitForConversion(args) {
810
+ const parsed = WaitForConversionSchema.parse(args);
811
+ const client = this.getClient();
812
+ const timeout = (parsed.timeout || 300) * 1000;
813
+ const pollInterval = (parsed.poll_interval || 2) * 1000;
814
+ const result = await client.waitForConversion(parsed.conversion_id, {
815
+ timeout,
816
+ pollInterval,
817
+ });
818
+ let text = `Conversion ${result.status}:\n` +
819
+ ` ID: ${result.id}\n` +
820
+ ` Status: ${result.status}\n` +
821
+ ` From: ${result.source_format} → ${result.target_format}\n` +
822
+ ` File: ${result.original_filename}\n`;
823
+ if (result.completed_at)
824
+ text += ` Completed: ${result.completed_at}\n`;
825
+ if (result.error_message)
826
+ text += ` Error: ${result.error_message}\n`;
827
+ if (result.status === "completed") {
828
+ text += `\nFile is ready! Use download_file to get the converted file.`;
829
+ }
830
+ return { content: [{ type: "text", text }] };
831
+ }
832
+ async handleDownloadFile(args) {
833
+ const parsed = DownloadFileSchema.parse(args);
834
+ const client = this.getClient();
835
+ const { data, filename, contentType } = await client.downloadFile(parsed.conversion_id);
836
+ if (parsed.save_path) {
837
+ // Validate save path
838
+ const savePath = path.resolve(parsed.save_path);
839
+ // Security: Check for null bytes
840
+ if (savePath.includes("\0")) {
841
+ throw new Error("Invalid save path: contains null bytes");
842
+ }
843
+ // Security: Ensure the path doesn't contain traversal sequences after resolution
844
+ if (savePath.includes("..")) {
845
+ throw new Error("Invalid save path: contains directory traversal");
846
+ }
847
+ // Determine final path (if directory, append filename)
848
+ const stats = fs.statSync(savePath, { throwIfNoEntry: false });
849
+ let finalPath;
850
+ if (stats?.isDirectory()) {
851
+ // Sanitize the filename before joining
852
+ const sanitizedFilename = path.basename(filename).replace(/[\x00-\x1f]/g, "");
853
+ finalPath = path.join(savePath, sanitizedFilename);
854
+ }
855
+ else {
856
+ // Ensure parent directory exists
857
+ const parentDir = path.dirname(savePath);
858
+ if (!fs.existsSync(parentDir)) {
859
+ throw new Error(`Parent directory does not exist: ${parentDir}`);
860
+ }
861
+ finalPath = savePath;
862
+ }
863
+ fs.writeFileSync(finalPath, data);
864
+ return {
865
+ content: [{
866
+ type: "text",
867
+ text: `File saved to: ${finalPath}\nSize: ${data.length} bytes\nType: ${contentType}`,
868
+ }],
869
+ };
870
+ }
871
+ const base64 = data.toString("base64");
872
+ return {
873
+ content: [{
874
+ type: "text",
875
+ text: `Filename: ${filename}\nSize: ${data.length} bytes\nType: ${contentType}\nData (base64):\n${base64}`,
876
+ }],
877
+ };
878
+ }
879
+ async handleListConversions(args) {
880
+ const parsed = ListConversionsSchema.parse(args);
881
+ const client = this.getClient();
882
+ const result = await client.listConversions(parsed.page, parsed.per_page);
883
+ let text = `Recent Conversions (page ${result.page}, ${result.conversions.length} of ${result.total}):\n\n`;
884
+ if (result.conversions.length === 0) {
885
+ text += "No conversions found.";
886
+ }
887
+ else {
888
+ for (const conv of result.conversions) {
889
+ text += `${conv.id}\n`;
890
+ text += ` ${conv.original_filename}: ${conv.source_format} → ${conv.target_format}\n`;
891
+ text += ` Status: ${conv.status}`;
892
+ if (conv.created_at)
893
+ text += ` | Created: ${conv.created_at}`;
894
+ text += "\n\n";
895
+ }
896
+ }
897
+ return { content: [{ type: "text", text }] };
898
+ }
899
+ async handleCancelConversion(args) {
900
+ const parsed = CancelConversionSchema.parse(args);
901
+ const client = this.getClient();
902
+ const result = await client.cancelConversion(parsed.conversion_id);
903
+ const text = result.success
904
+ ? `✓ ${result.message}`
905
+ : `✗ ${result.message}`;
906
+ return { content: [{ type: "text", text }] };
907
+ }
908
+ async handleGetFileInfo(args) {
909
+ const parsed = GetFileInfoSchema.parse(args);
910
+ const client = this.getClient();
911
+ const info = await client.getFileInfo(parsed.file_path);
912
+ const sizeFormatted = info.size < 1024
913
+ ? `${info.size} bytes`
914
+ : info.size < 1024 * 1024
915
+ ? `${(info.size / 1024).toFixed(1)} KB`
916
+ : info.size < 1024 * 1024 * 1024
917
+ ? `${(info.size / 1024 / 1024).toFixed(1)} MB`
918
+ : `${(info.size / 1024 / 1024 / 1024).toFixed(2)} GB`;
919
+ const text = `File Information:\n` +
920
+ ` Name: ${info.filename}\n` +
921
+ ` Size: ${sizeFormatted} (${info.size} bytes)\n` +
922
+ ` Format: ${info.format.toUpperCase()}\n` +
923
+ ` MIME Type: ${info.mimeType}`;
924
+ return { content: [{ type: "text", text }] };
925
+ }
926
+ async handleEstimateOutputSize(args) {
927
+ const parsed = EstimateOutputSizeSchema.parse(args);
928
+ const client = this.getClient();
929
+ const info = await client.getFileInfo(parsed.file_path);
930
+ const options = this.resolveOptions(parsed.target_format, parsed.preset, parsed.options);
931
+ const estimate = client.estimateOutputSize(info.size, info.format, parsed.target_format, options);
932
+ const formatSize = (size) => {
933
+ if (size < 1024)
934
+ return `${size} bytes`;
935
+ if (size < 1024 * 1024)
936
+ return `${(size / 1024).toFixed(1)} KB`;
937
+ if (size < 1024 * 1024 * 1024)
938
+ return `${(size / 1024 / 1024).toFixed(1)} MB`;
939
+ return `${(size / 1024 / 1024 / 1024).toFixed(2)} GB`;
940
+ };
941
+ const text = `Output Size Estimate:\n` +
942
+ ` Input: ${formatSize(info.size)} (${info.format.toUpperCase()})\n` +
943
+ ` Target: ${parsed.target_format.toUpperCase()}\n` +
944
+ ` Estimated Output: ${formatSize(estimate.estimatedSize)}\n` +
945
+ ` Confidence: ${estimate.confidence}\n` +
946
+ (estimate.notes ? ` Notes: ${estimate.notes}` : "");
947
+ return { content: [{ type: "text", text }] };
948
+ }
949
+ handleGetPrompt(name, args) {
950
+ switch (name) {
951
+ case "convert-for-web":
952
+ return {
953
+ messages: [
954
+ {
955
+ role: "user",
956
+ content: {
957
+ type: "text",
958
+ text: `I need to convert the file at "${args.file_path}" to a web-optimized format.
959
+
960
+ Please:
961
+ 1. First use get_file_info to check the file details
962
+ 2. Determine the best web format (e.g., JPG/WebP for images, MP4 for video, MP3 for audio)
963
+ 3. Use the "web-optimized" preset for optimal web delivery
964
+ 4. Convert the file and let me know when it's ready`,
965
+ },
966
+ },
967
+ ],
968
+ };
969
+ case "batch-convert-folder":
970
+ return {
971
+ messages: [
972
+ {
973
+ role: "user",
974
+ content: {
975
+ type: "text",
976
+ text: `Please convert all files in the folder "${args.folder_path}" to ${args.target_format} format.
977
+
978
+ Steps:
979
+ 1. List all files in the folder
980
+ 2. Filter for convertible files
981
+ 3. Use batch_convert to convert them all to ${args.target_format}
982
+ 4. Report the results when complete`,
983
+ },
984
+ },
985
+ ],
986
+ };
987
+ case "optimize-images":
988
+ const purpose = args.purpose || "web";
989
+ const preset = purpose === "print" ? "print-ready" : "web-optimized";
990
+ return {
991
+ messages: [
992
+ {
993
+ role: "user",
994
+ content: {
995
+ type: "text",
996
+ text: `Optimize the images at "${args.file_path}" for ${purpose} use.
997
+
998
+ Please:
999
+ 1. Check the image(s) using get_file_info
1000
+ 2. Use the "${preset}" preset
1001
+ 3. Convert to the appropriate format (WebP/JPG for web, PNG/TIFF for print)
1002
+ 4. Report the size savings`,
1003
+ },
1004
+ },
1005
+ ],
1006
+ };
1007
+ case "convert-video-for-streaming":
1008
+ const quality = args.quality || "medium";
1009
+ const crf = quality === "high" ? 18 : quality === "low" ? 28 : 23;
1010
+ return {
1011
+ messages: [
1012
+ {
1013
+ role: "user",
1014
+ content: {
1015
+ type: "text",
1016
+ text: `Convert the video at "${args.file_path}" to MP4 format optimized for streaming.
1017
+
1018
+ Requirements:
1019
+ 1. Get the video file info first
1020
+ 2. Convert to MP4 with CRF ${crf} (${quality} quality)
1021
+ 3. Use the "fast" preset for quicker encoding
1022
+ 4. Wait for completion and report the result`,
1023
+ },
1024
+ },
1025
+ ],
1026
+ };
1027
+ case "document-to-pdf":
1028
+ const pdfQuality = args.quality || "ebook";
1029
+ return {
1030
+ messages: [
1031
+ {
1032
+ role: "user",
1033
+ content: {
1034
+ type: "text",
1035
+ text: `Convert the document at "${args.file_path}" to PDF format.
1036
+
1037
+ Settings:
1038
+ 1. Use PDF quality: "${pdfQuality}"
1039
+ 2. Convert to PDF
1040
+ 3. Report when complete with the file size`,
1041
+ },
1042
+ },
1043
+ ],
1044
+ };
1045
+ default:
1046
+ throw new Error(`Unknown prompt: ${name}`);
1047
+ }
1048
+ }
1049
+ async run() {
1050
+ const transport = new stdio_js_1.StdioServerTransport();
1051
+ await this.server.connect(transport);
1052
+ console.error(`ConvertEverything MCP server v${PACKAGE_VERSION} running on stdio`);
1053
+ }
1054
+ }
1055
+ // ============================================================================
1056
+ // Main Entry Point
1057
+ // ============================================================================
1058
+ const server = new ConvertEverythingServer();
1059
+ server.run().catch((error) => {
1060
+ console.error("Fatal error:", error);
1061
+ process.exit(1);
1062
+ });
1063
+ //# sourceMappingURL=index.js.map