imagekit-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +128 -0
  3. package/index.js +352 -0
  4. package/package.json +47 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ronaldo Lima
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # ImageKit MCP Server
2
+
3
+ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for [ImageKit.io](https://imagekit.io) that lets AI assistants upload, search, transform, and manage media assets in your ImageKit media library.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npx imagekit-mcp-server
9
+ ```
10
+
11
+ Requires three environment variables:
12
+
13
+ ```bash
14
+ IMAGEKIT_PUBLIC_KEY=public_xxx
15
+ IMAGEKIT_PRIVATE_KEY=private_xxx
16
+ IMAGEKIT_URL_ENDPOINT=https://ik.imagekit.io/your_id
17
+ ```
18
+
19
+ Get your credentials from the [ImageKit Dashboard](https://imagekit.io/dashboard/developer/api-keys).
20
+
21
+ ## MCP Client Configuration
22
+
23
+ ### Claude Desktop
24
+
25
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "imagekit": {
31
+ "command": "npx",
32
+ "args": ["-y", "imagekit-mcp-server"],
33
+ "env": {
34
+ "IMAGEKIT_PUBLIC_KEY": "public_xxx",
35
+ "IMAGEKIT_PRIVATE_KEY": "private_xxx",
36
+ "IMAGEKIT_URL_ENDPOINT": "https://ik.imagekit.io/your_id"
37
+ }
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ ### Claude Code
44
+
45
+ Add to `~/.claude/settings.json`:
46
+
47
+ ```json
48
+ {
49
+ "mcpServers": {
50
+ "imagekit": {
51
+ "command": "npx",
52
+ "args": ["-y", "imagekit-mcp-server"],
53
+ "env": {
54
+ "IMAGEKIT_PUBLIC_KEY": "public_xxx",
55
+ "IMAGEKIT_PRIVATE_KEY": "private_xxx",
56
+ "IMAGEKIT_URL_ENDPOINT": "https://ik.imagekit.io/your_id"
57
+ }
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ ### Cursor
64
+
65
+ Add to `.cursor/mcp.json` in your project:
66
+
67
+ ```json
68
+ {
69
+ "mcpServers": {
70
+ "imagekit": {
71
+ "command": "npx",
72
+ "args": ["-y", "imagekit-mcp-server"],
73
+ "env": {
74
+ "IMAGEKIT_PUBLIC_KEY": "public_xxx",
75
+ "IMAGEKIT_PRIVATE_KEY": "private_xxx",
76
+ "IMAGEKIT_URL_ENDPOINT": "https://ik.imagekit.io/your_id"
77
+ }
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ ## Tools
84
+
85
+ | Tool | Description |
86
+ |------|-------------|
87
+ | `list_files` | List and search files with filters (path, type, search query, pagination, sort) |
88
+ | `list_folders` | List folders at a given path |
89
+ | `get_file_details` | Get full metadata for a file (dimensions, tags, URLs, dates) |
90
+ | `upload_file` | Upload a local file |
91
+ | `upload_file_from_url` | Upload from a remote URL |
92
+ | `upload_base64_file` | Upload from base64 data |
93
+ | `delete_file` | Delete a file by ID |
94
+ | `create_folder` | Create a new folder |
95
+ | `delete_folder` | Delete a folder |
96
+ | `move_file` | Move a file to a different folder |
97
+ | `generate_url` | Generate URLs with transformations (resize, crop, format, signed URLs) |
98
+
99
+ ## Examples
100
+
101
+ Ask your AI assistant:
102
+
103
+ - "List all images in the /products/ folder"
104
+ - "Upload this screenshot to /screenshots/2026/"
105
+ - "Generate a 300x300 thumbnail for /hero-banner.jpg"
106
+ - "Search for files named 'logo'"
107
+ - "Move /old-folder/image.jpg to /new-folder/"
108
+ - "Delete the file with ID abc123"
109
+
110
+ ## Development
111
+
112
+ ```bash
113
+ git clone https://github.com/limaronaldo/imagekit-mcp-server.git
114
+ cd imagekit-mcp-server
115
+ npm install
116
+
117
+ # Set credentials
118
+ export IMAGEKIT_PUBLIC_KEY=public_xxx
119
+ export IMAGEKIT_PRIVATE_KEY=private_xxx
120
+ export IMAGEKIT_URL_ENDPOINT=https://ik.imagekit.io/your_id
121
+
122
+ # Run
123
+ node index.js
124
+ ```
125
+
126
+ ## License
127
+
128
+ MIT
package/index.js ADDED
@@ -0,0 +1,352 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod";
6
+ import ImageKit from "imagekit";
7
+ import fs from "fs";
8
+ import path from "path";
9
+
10
+ // --- ImageKit client (lazy init, fail-fast on missing credentials) ---
11
+
12
+ let client = null;
13
+
14
+ function getClient() {
15
+ if (client) return client;
16
+
17
+ const publicKey = process.env.IMAGEKIT_PUBLIC_KEY;
18
+ const privateKey = process.env.IMAGEKIT_PRIVATE_KEY;
19
+ const urlEndpoint = process.env.IMAGEKIT_URL_ENDPOINT;
20
+
21
+ if (!publicKey || !privateKey || !urlEndpoint) {
22
+ throw new Error(
23
+ "Missing credentials. Set IMAGEKIT_PUBLIC_KEY, IMAGEKIT_PRIVATE_KEY, and IMAGEKIT_URL_ENDPOINT."
24
+ );
25
+ }
26
+
27
+ client = new ImageKit({ publicKey, privateKey, urlEndpoint });
28
+ return client;
29
+ }
30
+
31
+ // --- Helpers ---
32
+
33
+ function trimSlash(folder) {
34
+ if (!folder) return "/";
35
+ return folder.endsWith("/") ? folder.slice(0, -1) : folder;
36
+ }
37
+
38
+ function formatResult(text) {
39
+ return { content: [{ type: "text", text }] };
40
+ }
41
+
42
+ function formatError(message) {
43
+ return { content: [{ type: "text", text: message }], isError: true };
44
+ }
45
+
46
+ function formatFileInfo(file) {
47
+ const size = file.size ? `${(file.size / 1024).toFixed(1)} KB` : "N/A";
48
+ const dims =
49
+ file.width && file.height ? `${file.width}x${file.height}` : "";
50
+ const tags =
51
+ file.tags && file.tags.length > 0 ? `\n Tags: ${file.tags.join(", ")}` : "";
52
+ return `${file.name} (${file.fileType || "file"}, ${size}${dims ? ", " + dims : ""})${tags}\n ID: ${file.fileId}\n URL: ${file.url}`;
53
+ }
54
+
55
+ async function uploadToImageKit(fileData, fileName, options = {}) {
56
+ const ik = getClient();
57
+
58
+ const uploadOptions = {
59
+ file: fileData,
60
+ fileName,
61
+ folder: trimSlash(options.folder),
62
+ useUniqueFileName: options.useUniqueFileName ?? true,
63
+ isPrivateFile: options.isPrivateFile ?? false,
64
+ overwriteFile: options.overwriteFile ?? false,
65
+ };
66
+
67
+ if (options.tags?.length) uploadOptions.tags = options.tags;
68
+ if (options.customCoordinates) uploadOptions.customCoordinates = options.customCoordinates;
69
+ if (options.customMetadata) uploadOptions.customMetadata = options.customMetadata;
70
+
71
+ const result = await ik.upload(uploadOptions);
72
+
73
+ return formatResult(
74
+ `File uploaded successfully.\n\n` +
75
+ ` Name: ${result.name}\n` +
76
+ ` ID: ${result.fileId}\n` +
77
+ ` URL: ${result.url}\n` +
78
+ ` Size: ${(result.size / 1024).toFixed(1)} KB\n` +
79
+ ` Path: ${result.filePath}\n` +
80
+ ` Type: ${result.fileType}`
81
+ );
82
+ }
83
+
84
+ // --- Upload option schemas (shared) ---
85
+
86
+ const uploadOptionsSchema = {
87
+ folder: z.string().default("/").describe("Destination folder path"),
88
+ useUniqueFileName: z.boolean().default(true).describe("Generate unique filename to avoid conflicts"),
89
+ isPrivateFile: z.boolean().default(false).describe("Whether file should be private"),
90
+ overwriteFile: z.boolean().default(false).describe("Whether to overwrite existing file with same name"),
91
+ tags: z.array(z.string()).optional().describe("Tags to associate with the file"),
92
+ customCoordinates: z.string().optional().describe("Custom focus coordinates for cropping (x,y,width,height)"),
93
+ customMetadata: z.record(z.string(), z.any()).optional().describe("Custom metadata key-value pairs"),
94
+ };
95
+
96
+ // --- Server setup ---
97
+
98
+ const server = new McpServer({
99
+ name: "imagekit-mcp-server",
100
+ version: "1.0.0",
101
+ description: "MCP server for ImageKit.io media management",
102
+ });
103
+
104
+ // --- Tools ---
105
+
106
+ server.tool(
107
+ "list_files",
108
+ "List and search files in ImageKit media library",
109
+ {
110
+ path: z.string().default("/").describe("Folder path to list files from"),
111
+ searchQuery: z.string().optional().describe("Search query for file names (supports ImageKit search syntax)"),
112
+ limit: z.number().min(1).max(1000).default(20).describe("Number of files to return"),
113
+ skip: z.number().min(0).default(0).describe("Number of files to skip for pagination"),
114
+ fileType: z.enum(["image", "non-image", "all"]).default("all").describe("Filter by file type"),
115
+ sort: z.string().default("DESC_CREATED").describe("Sort order (ASC_CREATED, DESC_CREATED, ASC_NAME, DESC_NAME, ASC_SIZE, DESC_SIZE)"),
116
+ },
117
+ async ({ path: filePath, searchQuery, limit, skip, fileType, sort }) => {
118
+ const ik = getClient();
119
+
120
+ const params = {
121
+ path: filePath,
122
+ limit,
123
+ skip,
124
+ sort,
125
+ includeFolder: true,
126
+ };
127
+
128
+ if (searchQuery) params.searchQuery = searchQuery;
129
+ if (fileType !== "all") params.fileType = fileType;
130
+
131
+ const result = await ik.listFiles(params);
132
+ const count = result.length;
133
+
134
+ if (count === 0) {
135
+ return formatResult(`No files found in "${filePath}"${searchQuery ? ` matching "${searchQuery}"` : ""}.`);
136
+ }
137
+
138
+ const lines = result.map((f) => formatFileInfo(f));
139
+ const header = searchQuery
140
+ ? `Found ${count} file(s) matching "${searchQuery}" in "${filePath}":`
141
+ : `Found ${count} file(s) in "${filePath}":`;
142
+
143
+ return formatResult(`${header}\n\n${lines.join("\n\n")}`);
144
+ }
145
+ );
146
+
147
+ server.tool(
148
+ "list_folders",
149
+ "List folders in ImageKit",
150
+ {
151
+ path: z.string().default("/").describe("Folder path to list (default is root)"),
152
+ },
153
+ async ({ path: folderPath }) => {
154
+ const ik = getClient();
155
+
156
+ const result = await ik.listFiles({
157
+ path: folderPath,
158
+ includeFolder: true,
159
+ type: "folder",
160
+ });
161
+
162
+ const folders = result.filter((item) => item.type === "folder");
163
+
164
+ if (folders.length === 0) {
165
+ return formatResult(`No folders found in "${folderPath}".`);
166
+ }
167
+
168
+ const lines = folders.map(
169
+ (f) => `${f.name}\n ID: ${f.folderId}\n Path: ${f.folderPath}`
170
+ );
171
+
172
+ return formatResult(`Found ${folders.length} folder(s) in "${folderPath}":\n\n${lines.join("\n\n")}`);
173
+ }
174
+ );
175
+
176
+ server.tool(
177
+ "get_file_details",
178
+ "Get detailed information about a specific file",
179
+ {
180
+ fileId: z.string().describe("The unique ID of the file"),
181
+ },
182
+ async ({ fileId }) => {
183
+ const ik = getClient();
184
+ const f = await ik.getFileDetails(fileId);
185
+
186
+ const dims = f.width && f.height ? `\n Dimensions: ${f.width}x${f.height}` : "";
187
+ const tags = f.tags?.length ? `\n Tags: ${f.tags.join(", ")}` : "";
188
+
189
+ return formatResult(
190
+ `File details:\n\n` +
191
+ ` Name: ${f.name}\n` +
192
+ ` ID: ${f.fileId}\n` +
193
+ ` Type: ${f.fileType}\n` +
194
+ ` Size: ${(f.size / 1024).toFixed(1)} KB${dims}\n` +
195
+ ` URL: ${f.url}\n` +
196
+ ` Path: ${f.filePath}${tags}\n` +
197
+ ` Created: ${f.createdAt}\n` +
198
+ ` Updated: ${f.updatedAt}`
199
+ );
200
+ }
201
+ );
202
+
203
+ server.tool(
204
+ "upload_file",
205
+ "Upload a local file to ImageKit",
206
+ {
207
+ filePath: z.string().describe("Local path to the file to upload"),
208
+ fileName: z.string().optional().describe("Name for the uploaded file (defaults to original filename)"),
209
+ ...uploadOptionsSchema,
210
+ },
211
+ async ({ filePath, fileName, ...options }) => {
212
+ if (!fs.existsSync(filePath)) {
213
+ return formatError(`File not found: ${filePath}`);
214
+ }
215
+
216
+ const buffer = fs.readFileSync(filePath);
217
+ const base64 = buffer.toString("base64");
218
+ const name = fileName || path.basename(filePath);
219
+
220
+ return uploadToImageKit(base64, name, options);
221
+ }
222
+ );
223
+
224
+ server.tool(
225
+ "upload_file_from_url",
226
+ "Upload a file to ImageKit from a URL",
227
+ {
228
+ url: z.string().url().describe("URL of the file to upload"),
229
+ fileName: z.string().describe("Name for the uploaded file"),
230
+ ...uploadOptionsSchema,
231
+ },
232
+ async ({ url, fileName, ...options }) => {
233
+ return uploadToImageKit(url, fileName, options);
234
+ }
235
+ );
236
+
237
+ server.tool(
238
+ "upload_base64_file",
239
+ "Upload a file to ImageKit from base64 encoded data",
240
+ {
241
+ base64Data: z.string().describe("Base64 encoded file content (with or without data URI prefix)"),
242
+ fileName: z.string().describe("Name for the uploaded file"),
243
+ ...uploadOptionsSchema,
244
+ },
245
+ async ({ base64Data, fileName, ...options }) => {
246
+ // Strip data URI prefix if present
247
+ const clean = base64Data.startsWith("data:")
248
+ ? base64Data.split(",")[1]
249
+ : base64Data;
250
+
251
+ return uploadToImageKit(clean, fileName, options);
252
+ }
253
+ );
254
+
255
+ server.tool(
256
+ "delete_file",
257
+ "Delete a file from ImageKit",
258
+ {
259
+ fileId: z.string().describe("The unique ID of the file to delete"),
260
+ },
261
+ async ({ fileId }) => {
262
+ const ik = getClient();
263
+ await ik.deleteFile(fileId);
264
+ return formatResult(`File deleted successfully. ID: ${fileId}`);
265
+ }
266
+ );
267
+
268
+ server.tool(
269
+ "create_folder",
270
+ "Create a new folder in ImageKit",
271
+ {
272
+ folderName: z.string().describe("Name of the folder to create"),
273
+ parentFolderPath: z.string().default("/").describe("Parent folder path"),
274
+ },
275
+ async ({ folderName, parentFolderPath }) => {
276
+ const ik = getClient();
277
+ await ik.createFolder({ folderName, parentFolderPath });
278
+ return formatResult(`Folder "${folderName}" created in "${parentFolderPath}".`);
279
+ }
280
+ );
281
+
282
+ server.tool(
283
+ "delete_folder",
284
+ "Delete a folder from ImageKit",
285
+ {
286
+ folderPath: z.string().describe("Full path of the folder to delete"),
287
+ },
288
+ async ({ folderPath }) => {
289
+ const ik = getClient();
290
+ await ik.deleteFolder(folderPath);
291
+ return formatResult(`Folder deleted: ${folderPath}`);
292
+ }
293
+ );
294
+
295
+ server.tool(
296
+ "move_file",
297
+ "Move a file to a different folder in ImageKit",
298
+ {
299
+ sourceFilePath: z.string().describe("Current full path of the file (e.g., /folder/image.jpg)"),
300
+ destinationPath: z.string().describe("Destination folder path (e.g., /new-folder/)"),
301
+ },
302
+ async ({ sourceFilePath, destinationPath }) => {
303
+ const ik = getClient();
304
+ await ik.moveFile({ sourceFilePath, destinationPath });
305
+ return formatResult(`File moved from "${sourceFilePath}" to "${destinationPath}".`);
306
+ }
307
+ );
308
+
309
+ server.tool(
310
+ "generate_url",
311
+ "Generate an ImageKit URL with transformations (resize, crop, format, quality, etc.)",
312
+ {
313
+ path: z.string().optional().describe("Image path relative to URL endpoint (e.g., /folder/image.jpg)"),
314
+ src: z.string().optional().describe("Absolute image URL (alternative to path)"),
315
+ transformation: z
316
+ .array(z.record(z.string(), z.any()))
317
+ .optional()
318
+ .describe("Array of transformation objects (e.g., [{height: 300, width: 300}])"),
319
+ transformationPosition: z.enum(["path", "query"]).default("path").describe("Where to place transformation params"),
320
+ signed: z.boolean().default(false).describe("Generate a signed URL"),
321
+ expireSeconds: z.number().default(300).describe("Signed URL expiry in seconds"),
322
+ },
323
+ async ({ path: imgPath, src, transformation, transformationPosition, signed, expireSeconds }) => {
324
+ if (!imgPath && !src) {
325
+ return formatError("Either 'path' or 'src' is required.");
326
+ }
327
+
328
+ const ik = getClient();
329
+
330
+ const options = { transformation_position: transformationPosition };
331
+ if (imgPath) options.path = imgPath;
332
+ if (src) options.src = src;
333
+ if (transformation) options.transformation = transformation;
334
+ if (signed) {
335
+ options.signed = true;
336
+ options.expire_seconds = expireSeconds;
337
+ }
338
+
339
+ const url = ik.url(options);
340
+
341
+ return formatResult(
342
+ `Generated URL:\n\n ${url}` +
343
+ (signed ? `\n\n Signed: yes, expires in ${expireSeconds}s` : "")
344
+ );
345
+ }
346
+ );
347
+
348
+ // --- Start ---
349
+
350
+ const transport = new StdioServerTransport();
351
+ await server.connect(transport);
352
+ console.error("ImageKit MCP server running on stdio");
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "imagekit-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for ImageKit.io — upload, search, transform, and manage media assets",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "imagekit-mcp-server": "index.js"
9
+ },
10
+ "files": [
11
+ "index.js",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "start": "node index.js"
17
+ },
18
+ "dependencies": {
19
+ "@modelcontextprotocol/sdk": "^1.26.0",
20
+ "imagekit": "^6.0.0",
21
+ "zod": "^3.24.0"
22
+ },
23
+ "keywords": [
24
+ "mcp",
25
+ "mcp-server",
26
+ "imagekit",
27
+ "media",
28
+ "cdn",
29
+ "image-management",
30
+ "model-context-protocol",
31
+ "ai",
32
+ "claude"
33
+ ],
34
+ "author": "Ronaldo Lima",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/limaronaldo/imagekit-mcp-server.git"
39
+ },
40
+ "homepage": "https://github.com/limaronaldo/imagekit-mcp-server#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/limaronaldo/imagekit-mcp-server/issues"
43
+ },
44
+ "engines": {
45
+ "node": ">=18"
46
+ }
47
+ }