@agimon-ai/imagine-mcp 0.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.
@@ -0,0 +1,957 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ let __modelcontextprotocol_sdk_server_index_js = require("@modelcontextprotocol/sdk/server/index.js");
25
+ let __modelcontextprotocol_sdk_types_js = require("@modelcontextprotocol/sdk/types.js");
26
+ let node_crypto = require("node:crypto");
27
+ let node_fs = require("node:fs");
28
+ let node_fs_promises = require("node:fs/promises");
29
+ let node_path = require("node:path");
30
+ let node_fetch = require("node-fetch");
31
+ node_fetch = __toESM(node_fetch);
32
+ let unsplash_js = require("unsplash-js");
33
+ let zod = require("zod");
34
+ let __modelcontextprotocol_sdk_server_streamableHttp_js = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
35
+ let express = require("express");
36
+ express = __toESM(express);
37
+ let __modelcontextprotocol_sdk_server_sse_js = require("@modelcontextprotocol/sdk/server/sse.js");
38
+ let __modelcontextprotocol_sdk_server_stdio_js = require("@modelcontextprotocol/sdk/server/stdio.js");
39
+
40
+ //#region src/utils.ts
41
+ /**
42
+ * Normalizes a file path by handling escaped characters and spaces
43
+ *
44
+ * This function handles cases like 'a\ name.png' by converting them to 'a name.png'
45
+ */
46
+ function normalizeFilePath(filePath) {
47
+ let normalizedPath = filePath.replace(/\\+ /g, " ");
48
+ normalizedPath = normalizedPath.replace(/\\+'/g, "'").replace(/\\+"/g, "\"").replace(/\\+`/g, "`").replace(/\\+\(/g, "(").replace(/\\+\)/g, ")").replace(/\\+\[/g, "[").replace(/\\+\]/g, "]").replace(/\\+\{/g, "{").replace(/\\+\}/g, "}");
49
+ return normalizedPath;
50
+ }
51
+
52
+ //#endregion
53
+ //#region src/services/ImageReader.ts
54
+ const DEFAULT_MIME_TYPE = "application/octet-stream";
55
+ const MIME_BY_EXTENSION = {
56
+ ".png": "image/png",
57
+ ".jpg": "image/jpeg",
58
+ ".jpeg": "image/jpeg",
59
+ ".gif": "image/gif",
60
+ ".webp": "image/webp",
61
+ ".bmp": "image/bmp",
62
+ ".svg": "image/svg+xml",
63
+ ".heic": "image/heic",
64
+ ".heif": "image/heif"
65
+ };
66
+ var ImageReader = class {
67
+ async readImage(source, options = {}) {
68
+ const imageSource = await this.resolveImageSource(source);
69
+ const sha256 = (0, node_crypto.createHash)("sha256").update(imageSource.data).digest("hex");
70
+ const result = {
71
+ source: imageSource.source,
72
+ sourceType: imageSource.sourceType,
73
+ mimeType: imageSource.mimeType,
74
+ sizeBytes: imageSource.sizeBytes,
75
+ sha256
76
+ };
77
+ if (imageSource.sourceType !== "data-uri") result.fileName = (0, node_path.basename)(imageSource.source);
78
+ if (options.includeBase64) result.base64 = imageSource.data.toString("base64");
79
+ return result;
80
+ }
81
+ async resolveImageSource(source) {
82
+ const trimmed = source.trim();
83
+ if (!trimmed) throw new __modelcontextprotocol_sdk_types_js.McpError(__modelcontextprotocol_sdk_types_js.ErrorCode.InvalidParams, "Image source cannot be empty.");
84
+ if (trimmed.startsWith("data:")) return this.resolveDataUri(trimmed);
85
+ if (/^https?:\/\//i.test(trimmed)) return this.resolveRemoteImage(trimmed);
86
+ return this.resolveFile(trimmed);
87
+ }
88
+ async resolveFile(filePath) {
89
+ const normalizedPath = (0, node_path.resolve)(normalizeFilePath(filePath));
90
+ try {
91
+ await (0, node_fs_promises.access)(normalizedPath, node_fs.constants.F_OK);
92
+ } catch (error) {
93
+ throw new __modelcontextprotocol_sdk_types_js.McpError(__modelcontextprotocol_sdk_types_js.ErrorCode.InvalidParams, `Image file not found at path: ${filePath}`);
94
+ }
95
+ let data;
96
+ try {
97
+ data = await (0, node_fs_promises.readFile)(normalizedPath);
98
+ } catch (error) {
99
+ throw new __modelcontextprotocol_sdk_types_js.McpError(__modelcontextprotocol_sdk_types_js.ErrorCode.InternalError, `Failed to read image file: ${error instanceof Error ? error.message : String(error)}`);
100
+ }
101
+ return {
102
+ source: normalizedPath,
103
+ sourceType: "file",
104
+ mimeType: MIME_BY_EXTENSION[(0, node_path.extname)(normalizedPath).toLowerCase()] || DEFAULT_MIME_TYPE,
105
+ sizeBytes: data.length,
106
+ data
107
+ };
108
+ }
109
+ async resolveRemoteImage(url) {
110
+ try {
111
+ const response = await (0, node_fetch.default)(url, { redirect: "follow" });
112
+ if (!response.ok) throw new __modelcontextprotocol_sdk_types_js.McpError(__modelcontextprotocol_sdk_types_js.ErrorCode.InvalidParams, `Failed to fetch image from URL: ${url} (${response.status} ${response.statusText})`);
113
+ const arrayBuffer = await response.arrayBuffer();
114
+ const data = Buffer.from(arrayBuffer);
115
+ const contentType = response.headers.get("content-type")?.split(";")[0];
116
+ const fileName = (0, node_path.basename)(new URL(url).pathname || "image");
117
+ return {
118
+ source: url,
119
+ sourceType: "url",
120
+ mimeType: contentType ?? MIME_BY_EXTENSION[(0, node_path.extname)(fileName)] ?? DEFAULT_MIME_TYPE,
121
+ sizeBytes: data.length,
122
+ data
123
+ };
124
+ } catch (error) {
125
+ if (error instanceof __modelcontextprotocol_sdk_types_js.McpError) throw error;
126
+ throw new __modelcontextprotocol_sdk_types_js.McpError(__modelcontextprotocol_sdk_types_js.ErrorCode.InternalError, `Failed to download image from URL: ${error instanceof Error ? error.message : String(error)}`);
127
+ }
128
+ }
129
+ resolveDataUri(dataUri) {
130
+ const commaIndex = dataUri.indexOf(",");
131
+ if (commaIndex === -1 || commaIndex === dataUri.length - 1) throw new __modelcontextprotocol_sdk_types_js.McpError(__modelcontextprotocol_sdk_types_js.ErrorCode.InvalidParams, "Invalid data URI format. Expected `data:[<mime>];base64,<payload>`.");
132
+ const metadata = dataUri.slice(5, commaIndex).toLowerCase();
133
+ const payload = dataUri.slice(commaIndex + 1);
134
+ const isBase64 = metadata.includes("base64");
135
+ const declaredMimeType = metadata.split(";")[0] || DEFAULT_MIME_TYPE;
136
+ try {
137
+ const data = isBase64 ? Buffer.from(payload, "base64") : Buffer.from(decodeURIComponent(payload));
138
+ return {
139
+ source: dataUri,
140
+ sourceType: "data-uri",
141
+ mimeType: declaredMimeType || DEFAULT_MIME_TYPE,
142
+ sizeBytes: data.length,
143
+ data
144
+ };
145
+ } catch (error) {
146
+ throw new __modelcontextprotocol_sdk_types_js.McpError(__modelcontextprotocol_sdk_types_js.ErrorCode.InvalidParams, `Failed to decode data URI payload: ${error instanceof Error ? error.message : String(error)}`);
147
+ }
148
+ }
149
+ };
150
+
151
+ //#endregion
152
+ //#region src/tools/ReadImageTool.ts
153
+ var ReadImageTool = class ReadImageTool {
154
+ static TOOL_NAME = "read-image";
155
+ service = new ImageReader();
156
+ getDefinition() {
157
+ return {
158
+ name: ReadImageTool.TOOL_NAME,
159
+ description: "Read an image from a local path, URL, or data URI and return metadata.",
160
+ inputSchema: {
161
+ type: "object",
162
+ properties: {
163
+ source: {
164
+ type: "string",
165
+ minLength: 1,
166
+ description: "Image source as a local path, HTTP(S) URL, or data URI."
167
+ },
168
+ includeBase64: {
169
+ type: "boolean",
170
+ description: "Whether to include base64-encoded output.",
171
+ default: false
172
+ }
173
+ },
174
+ required: ["source"],
175
+ additionalProperties: false
176
+ }
177
+ };
178
+ }
179
+ async execute(input) {
180
+ try {
181
+ if (!input || typeof input !== "object") throw new __modelcontextprotocol_sdk_types_js.McpError(__modelcontextprotocol_sdk_types_js.ErrorCode.InvalidParams, "Tool input must be an object.");
182
+ const payload = input;
183
+ const source = payload.source;
184
+ const includeBase64 = payload.includeBase64;
185
+ if (!source || typeof source !== "string" || source.trim().length === 0) throw new __modelcontextprotocol_sdk_types_js.McpError(__modelcontextprotocol_sdk_types_js.ErrorCode.InvalidParams, "source must be a non-empty string.");
186
+ if (includeBase64 !== void 0 && typeof includeBase64 !== "boolean") throw new __modelcontextprotocol_sdk_types_js.McpError(__modelcontextprotocol_sdk_types_js.ErrorCode.InvalidParams, "includeBase64 must be a boolean.");
187
+ const options = { includeBase64 };
188
+ const result = await this.service.readImage(source, options);
189
+ return { content: [{
190
+ type: "text",
191
+ text: JSON.stringify(result, null, 2)
192
+ }] };
193
+ } catch (error) {
194
+ return {
195
+ content: [{
196
+ type: "text",
197
+ text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`
198
+ }],
199
+ isError: true
200
+ };
201
+ }
202
+ }
203
+ };
204
+
205
+ //#endregion
206
+ //#region src/imagineEnvSchema.ts
207
+ /**
208
+ * Environment variable schema for imagine-mcp
209
+ *
210
+ * Following Agiflow naming conventions:
211
+ * - Service credentials use provider-specific prefixes
212
+ * - Upload service selection uses UPLOAD_SERVICE
213
+ */
214
+ const envSchema = zod.z.object({
215
+ UPLOAD_SERVICE: zod.z.enum([
216
+ "s3",
217
+ "cloudflare",
218
+ "gcloud"
219
+ ]).optional().default("s3"),
220
+ S3_BUCKET: zod.z.string().optional(),
221
+ AWS_ACCESS_KEY_ID: zod.z.string().optional(),
222
+ AWS_SECRET_ACCESS_KEY: zod.z.string().optional(),
223
+ S3_REGION: zod.z.string().optional().default("us-east-1"),
224
+ S3_ENDPOINT: zod.z.string().url("S3_ENDPOINT must be a valid URL").optional(),
225
+ CLOUDFLARE_R2_BUCKET: zod.z.string().optional(),
226
+ CLOUDFLARE_R2_ACCESS_KEY_ID: zod.z.string().optional(),
227
+ CLOUDFLARE_R2_SECRET_ACCESS_KEY: zod.z.string().optional(),
228
+ CLOUDFLARE_R2_REGION: zod.z.string().optional().default("auto"),
229
+ CLOUDFLARE_R2_ENDPOINT: zod.z.string().url("CLOUDFLARE_R2_ENDPOINT must be a valid URL").optional(),
230
+ GCLOUD_BUCKET: zod.z.string().optional(),
231
+ GCLOUD_PROJECT_ID: zod.z.string().optional(),
232
+ GCLOUD_CREDENTIALS_PATH: zod.z.string().optional(),
233
+ UNSPLASH_ACCESS_KEY: zod.z.string().optional()
234
+ }).refine((data) => {
235
+ if (data.AWS_ACCESS_KEY_ID || data.AWS_SECRET_ACCESS_KEY) return data.AWS_ACCESS_KEY_ID && data.AWS_SECRET_ACCESS_KEY;
236
+ return true;
237
+ }, {
238
+ message: "Both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be provided together",
239
+ path: ["AWS_ACCESS_KEY_ID"]
240
+ });
241
+
242
+ //#endregion
243
+ //#region src/config.ts
244
+ /**
245
+ * Centralized configuration service for imagine-mcp
246
+ *
247
+ * Validates environment variables at startup and provides
248
+ * type-safe access to configuration throughout the application.
249
+ */
250
+ var ConfigService = class ConfigService {
251
+ static instance;
252
+ config;
253
+ constructor() {
254
+ try {
255
+ this.config = envSchema.parse(process.env);
256
+ } catch (error) {
257
+ if (error instanceof zod.z.ZodError) {
258
+ const errorMessage = error.issues.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ");
259
+ throw new __modelcontextprotocol_sdk_types_js.McpError(__modelcontextprotocol_sdk_types_js.ErrorCode.InternalError, `Environment configuration validation failed: ${errorMessage}`);
260
+ }
261
+ throw error;
262
+ }
263
+ }
264
+ /**
265
+ * Get the singleton instance of ConfigService
266
+ */
267
+ static getInstance() {
268
+ if (!ConfigService.instance) ConfigService.instance = new ConfigService();
269
+ return ConfigService.instance;
270
+ }
271
+ /**
272
+ * Get the validated configuration
273
+ */
274
+ getConfig() {
275
+ return this.config;
276
+ }
277
+ /**
278
+ * Get S3 configuration
279
+ */
280
+ getS3Config() {
281
+ return {
282
+ bucket: this.config.S3_BUCKET,
283
+ accessKeyId: this.config.AWS_ACCESS_KEY_ID,
284
+ secretAccessKey: this.config.AWS_SECRET_ACCESS_KEY,
285
+ region: this.config.S3_REGION,
286
+ endpoint: this.config.S3_ENDPOINT
287
+ };
288
+ }
289
+ /**
290
+ * Get Cloudflare R2 configuration
291
+ */
292
+ getCloudflareConfig() {
293
+ return {
294
+ bucket: this.config.CLOUDFLARE_R2_BUCKET,
295
+ accessKeyId: this.config.CLOUDFLARE_R2_ACCESS_KEY_ID,
296
+ secretAccessKey: this.config.CLOUDFLARE_R2_SECRET_ACCESS_KEY,
297
+ region: this.config.CLOUDFLARE_R2_REGION,
298
+ endpoint: this.config.CLOUDFLARE_R2_ENDPOINT
299
+ };
300
+ }
301
+ /**
302
+ * Get Google Cloud Storage configuration
303
+ */
304
+ getGCloudConfig() {
305
+ return {
306
+ bucket: this.config.GCLOUD_BUCKET,
307
+ projectId: this.config.GCLOUD_PROJECT_ID,
308
+ credentialsPath: this.config.GCLOUD_CREDENTIALS_PATH
309
+ };
310
+ }
311
+ /**
312
+ * Get the default upload service
313
+ */
314
+ getUploadService() {
315
+ return this.config.UPLOAD_SERVICE;
316
+ }
317
+ /**
318
+ * Get Unsplash API configuration
319
+ */
320
+ getUnsplashConfig() {
321
+ return { accessKey: this.config.UNSPLASH_ACCESS_KEY };
322
+ }
323
+ /**
324
+ * Validate that required credentials exist for a specific service
325
+ */
326
+ validateServiceConfig(service) {
327
+ switch (service) {
328
+ case "s3":
329
+ if (!this.config.S3_BUCKET) throw new __modelcontextprotocol_sdk_types_js.McpError(__modelcontextprotocol_sdk_types_js.ErrorCode.InvalidParams, "S3_BUCKET is required for S3 upload service");
330
+ break;
331
+ case "cloudflare":
332
+ if (!this.config.CLOUDFLARE_R2_BUCKET) throw new __modelcontextprotocol_sdk_types_js.McpError(__modelcontextprotocol_sdk_types_js.ErrorCode.InvalidParams, "CLOUDFLARE_R2_BUCKET is required for Cloudflare upload service");
333
+ if (!this.config.CLOUDFLARE_R2_ACCESS_KEY_ID || !this.config.CLOUDFLARE_R2_SECRET_ACCESS_KEY) throw new __modelcontextprotocol_sdk_types_js.McpError(__modelcontextprotocol_sdk_types_js.ErrorCode.InvalidParams, "CLOUDFLARE_R2_ACCESS_KEY_ID and CLOUDFLARE_R2_SECRET_ACCESS_KEY are required for Cloudflare upload service");
334
+ if (!this.config.CLOUDFLARE_R2_ENDPOINT) throw new __modelcontextprotocol_sdk_types_js.McpError(__modelcontextprotocol_sdk_types_js.ErrorCode.InvalidParams, "CLOUDFLARE_R2_ENDPOINT is required for Cloudflare upload service");
335
+ break;
336
+ case "gcloud":
337
+ if (!this.config.GCLOUD_BUCKET) throw new __modelcontextprotocol_sdk_types_js.McpError(__modelcontextprotocol_sdk_types_js.ErrorCode.InvalidParams, "GCLOUD_BUCKET is required for Google Cloud upload service");
338
+ if (!this.config.GCLOUD_PROJECT_ID) throw new __modelcontextprotocol_sdk_types_js.McpError(__modelcontextprotocol_sdk_types_js.ErrorCode.InvalidParams, "GCLOUD_PROJECT_ID is required for Google Cloud upload service");
339
+ break;
340
+ }
341
+ }
342
+ };
343
+ const config = ConfigService.getInstance();
344
+
345
+ //#endregion
346
+ //#region src/services/UnsplashService.ts
347
+ /**
348
+ * UnsplashService
349
+ *
350
+ * DESIGN PATTERNS:
351
+ * - Service pattern for business logic encapsulation
352
+ * - Single responsibility principle
353
+ *
354
+ * CODING STANDARDS:
355
+ * - Use async/await for asynchronous operations
356
+ * - Throw descriptive errors for error cases
357
+ * - Keep methods focused and well-named
358
+ * - Document complex logic with comments
359
+ *
360
+ * AVOID:
361
+ * - Mixing concerns (keep focused on single domain)
362
+ * - Direct tool implementation (services should be tool-agnostic)
363
+ */
364
+ var UnsplashService = class {
365
+ api;
366
+ constructor(accessKey) {
367
+ const unsplashConfig = config.getUnsplashConfig();
368
+ const apiKey = accessKey || unsplashConfig.accessKey;
369
+ if (!apiKey) throw new Error("Unsplash API key is required. Set UNSPLASH_ACCESS_KEY environment variable.");
370
+ this.api = (0, unsplash_js.createApi)({ accessKey: apiKey });
371
+ }
372
+ async searchImages(params) {
373
+ try {
374
+ const result = await this.api.search.getPhotos({
375
+ query: params.query,
376
+ page: params.page || 1,
377
+ perPage: params.perPage || 10,
378
+ orientation: params.orientation,
379
+ color: params.color
380
+ });
381
+ if (result.errors) throw new Error(`Unsplash API error: ${result.errors.join(", ")}`);
382
+ if (!result.response) throw new Error("No response from Unsplash API");
383
+ return result.response.results.map((photo) => ({
384
+ id: photo.id,
385
+ description: photo.description,
386
+ alt_description: photo.alt_description,
387
+ urls: {
388
+ raw: photo.urls.raw,
389
+ full: photo.urls.full,
390
+ regular: photo.urls.regular,
391
+ small: photo.urls.small,
392
+ thumb: photo.urls.thumb
393
+ },
394
+ links: {
395
+ html: photo.links.html,
396
+ download: photo.links.download
397
+ },
398
+ user: {
399
+ name: photo.user.name,
400
+ username: photo.user.username,
401
+ portfolio_url: photo.user.portfolio_url
402
+ },
403
+ width: photo.width,
404
+ height: photo.height,
405
+ color: photo.color || "#000000",
406
+ likes: photo.likes
407
+ }));
408
+ } catch (error) {
409
+ throw new Error(`Failed to search Unsplash images: ${error instanceof Error ? error.message : "Unknown error"}`);
410
+ }
411
+ }
412
+ };
413
+
414
+ //#endregion
415
+ //#region src/tools/UnsplashSearchTool.ts
416
+ var UnsplashSearchTool = class UnsplashSearchTool {
417
+ static TOOL_NAME = "unsplash_search";
418
+ service;
419
+ accessKey;
420
+ constructor(accessKey) {
421
+ this.accessKey = accessKey;
422
+ this.service = null;
423
+ }
424
+ getService() {
425
+ if (!this.service) this.service = new UnsplashService(this.accessKey);
426
+ return this.service;
427
+ }
428
+ getDefinition() {
429
+ return {
430
+ name: UnsplashSearchTool.TOOL_NAME,
431
+ description: "Search for stock images from Unsplash by keyword and return image URLs with metadata",
432
+ inputSchema: {
433
+ type: "object",
434
+ properties: {
435
+ query: {
436
+ type: "string",
437
+ description: "Search query (e.g., \"sunset\", \"technology\", \"nature\")"
438
+ },
439
+ perPage: {
440
+ type: "number",
441
+ description: "Number of results per page (1-30, default: 10)",
442
+ minimum: 1,
443
+ maximum: 30
444
+ },
445
+ page: {
446
+ type: "number",
447
+ description: "Page number for pagination (default: 1)",
448
+ minimum: 1
449
+ },
450
+ orientation: {
451
+ type: "string",
452
+ enum: [
453
+ "landscape",
454
+ "portrait",
455
+ "squarish"
456
+ ],
457
+ description: "Filter by photo orientation"
458
+ },
459
+ color: {
460
+ type: "string",
461
+ enum: [
462
+ "black_and_white",
463
+ "black",
464
+ "white",
465
+ "yellow",
466
+ "orange",
467
+ "red",
468
+ "purple",
469
+ "magenta",
470
+ "green",
471
+ "teal",
472
+ "blue"
473
+ ],
474
+ description: "Filter by photo color"
475
+ }
476
+ },
477
+ required: ["query"],
478
+ additionalProperties: false
479
+ }
480
+ };
481
+ }
482
+ async execute(input) {
483
+ try {
484
+ const images = await this.getService().searchImages({
485
+ query: input.query,
486
+ perPage: input.perPage,
487
+ page: input.page,
488
+ orientation: input.orientation,
489
+ color: input.color
490
+ });
491
+ if (images.length === 0) return { content: [{
492
+ type: "text",
493
+ text: `No images found for query: "${input.query}"`
494
+ }] };
495
+ const formattedResults = images.map((img, index) => {
496
+ return `
497
+ 📷 **Image ${index + 1}**
498
+ - **ID**: ${img.id}
499
+ - **Description**: ${img.alt_description || img.description || "No description"}
500
+ - **Photographer**: ${img.user.name} (@${img.user.username})
501
+ - **Dimensions**: ${img.width}x${img.height}px
502
+ - **Color**: ${img.color}
503
+ - **Likes**: ${img.likes}
504
+
505
+ **URLs**:
506
+ - Full: ${img.urls.full}
507
+ - Regular: ${img.urls.regular}
508
+ - Small: ${img.urls.small}
509
+ - Thumbnail: ${img.urls.thumb}
510
+
511
+ **Links**:
512
+ - View on Unsplash: ${img.links.html}
513
+ - Download: ${img.links.download}
514
+ ${img.user.portfolio_url ? `- Photographer Portfolio: ${img.user.portfolio_url}` : ""}
515
+ `.trim();
516
+ }).join("\n\n" + "-".repeat(80) + "\n\n");
517
+ return { content: [{
518
+ type: "text",
519
+ text: `Found ${images.length} image(s) for "${input.query}":\n\n${formattedResults}`
520
+ }] };
521
+ } catch (error) {
522
+ return {
523
+ content: [{
524
+ type: "text",
525
+ text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`
526
+ }],
527
+ isError: true
528
+ };
529
+ }
530
+ }
531
+ };
532
+
533
+ //#endregion
534
+ //#region src/server/index.ts
535
+ /**
536
+ * MCP Server Setup
537
+ *
538
+ * DESIGN PATTERNS:
539
+ * - Factory pattern for server creation
540
+ * - Tool registration pattern
541
+ *
542
+ * CODING STANDARDS:
543
+ * - Register all tools, resources, and prompts here
544
+ * - Keep server setup modular and extensible
545
+ * - Import tools from ../tools/ and register them in the handlers
546
+ */
547
+ function createServer() {
548
+ const server = new __modelcontextprotocol_sdk_server_index_js.Server({
549
+ name: "imagine-mcp",
550
+ version: "0.1.0"
551
+ }, { capabilities: { tools: {} } });
552
+ const unsplashSearchTool = new UnsplashSearchTool();
553
+ const readImageTool = new ReadImageTool();
554
+ server.setRequestHandler(__modelcontextprotocol_sdk_types_js.ListToolsRequestSchema, async () => ({ tools: [unsplashSearchTool.getDefinition(), readImageTool.getDefinition()] }));
555
+ server.setRequestHandler(__modelcontextprotocol_sdk_types_js.CallToolRequestSchema, async (request) => {
556
+ const { name, arguments: args } = request.params;
557
+ if (name === UnsplashSearchTool.TOOL_NAME) return await unsplashSearchTool.execute(args);
558
+ if (name === ReadImageTool.TOOL_NAME) return await readImageTool.execute(args);
559
+ return {
560
+ content: [{
561
+ type: "text",
562
+ text: `Unknown tool: ${name}`
563
+ }],
564
+ isError: true
565
+ };
566
+ });
567
+ return server;
568
+ }
569
+
570
+ //#endregion
571
+ //#region src/transports/http.ts
572
+ /**
573
+ * HTTP Transport Handler
574
+ *
575
+ * DESIGN PATTERNS:
576
+ * - Transport handler pattern implementing TransportHandler interface
577
+ * - Session management for stateful connections
578
+ * - Streamable HTTP protocol (2025-03-26) with resumability support
579
+ * - Factory pattern for creating MCP server instances per session
580
+ *
581
+ * CODING STANDARDS:
582
+ * - Use async/await for all asynchronous operations
583
+ * - Implement proper session lifecycle management
584
+ * - Handle errors gracefully with appropriate HTTP status codes
585
+ * - Provide health check endpoint for monitoring
586
+ * - Clean up resources on shutdown
587
+ *
588
+ * AVOID:
589
+ * - Sharing MCP server instances across sessions (use factory pattern)
590
+ * - Forgetting to clean up sessions on disconnect
591
+ * - Missing error handling for request processing
592
+ * - Hardcoded configuration (use TransportConfig)
593
+ */
594
+ /**
595
+ * HTTP session manager
596
+ */
597
+ var HttpFullSessionManager = class {
598
+ sessions = /* @__PURE__ */ new Map();
599
+ getSession(sessionId) {
600
+ return this.sessions.get(sessionId);
601
+ }
602
+ setSession(sessionId, transport, server) {
603
+ this.sessions.set(sessionId, {
604
+ transport,
605
+ server
606
+ });
607
+ }
608
+ deleteSession(sessionId) {
609
+ const session = this.sessions.get(sessionId);
610
+ if (session) session.server.close();
611
+ this.sessions.delete(sessionId);
612
+ }
613
+ hasSession(sessionId) {
614
+ return this.sessions.has(sessionId);
615
+ }
616
+ clear() {
617
+ for (const session of this.sessions.values()) session.server.close();
618
+ this.sessions.clear();
619
+ }
620
+ };
621
+ /**
622
+ * HTTP transport handler using Streamable HTTP (protocol version 2025-03-26)
623
+ * Provides stateful session management with resumability support
624
+ */
625
+ var HttpTransportHandler = class {
626
+ serverFactory;
627
+ app;
628
+ server = null;
629
+ sessionManager;
630
+ config;
631
+ constructor(serverFactory, config$1) {
632
+ this.serverFactory = typeof serverFactory === "function" ? serverFactory : () => serverFactory;
633
+ this.app = (0, express.default)();
634
+ this.sessionManager = new HttpFullSessionManager();
635
+ this.config = {
636
+ mode: config$1.mode,
637
+ port: config$1.port ?? 3e3,
638
+ host: config$1.host ?? "localhost"
639
+ };
640
+ this.setupMiddleware();
641
+ this.setupRoutes();
642
+ }
643
+ setupMiddleware() {
644
+ this.app.use(express.default.json());
645
+ }
646
+ setupRoutes() {
647
+ this.app.post("/mcp", async (req, res) => {
648
+ await this.handlePostRequest(req, res);
649
+ });
650
+ this.app.get("/mcp", async (req, res) => {
651
+ await this.handleGetRequest(req, res);
652
+ });
653
+ this.app.delete("/mcp", async (req, res) => {
654
+ await this.handleDeleteRequest(req, res);
655
+ });
656
+ this.app.get("/health", (_req, res) => {
657
+ res.json({
658
+ status: "ok",
659
+ transport: "http"
660
+ });
661
+ });
662
+ }
663
+ async handlePostRequest(req, res) {
664
+ const sessionId = req.headers["mcp-session-id"];
665
+ let transport;
666
+ if (sessionId && this.sessionManager.hasSession(sessionId)) transport = this.sessionManager.getSession(sessionId).transport;
667
+ else if (!sessionId && (0, __modelcontextprotocol_sdk_types_js.isInitializeRequest)(req.body)) {
668
+ const mcpServer = this.serverFactory();
669
+ transport = new __modelcontextprotocol_sdk_server_streamableHttp_js.StreamableHTTPServerTransport({
670
+ sessionIdGenerator: () => (0, node_crypto.randomUUID)(),
671
+ enableJsonResponse: true,
672
+ onsessioninitialized: (sessionId$1) => {
673
+ this.sessionManager.setSession(sessionId$1, transport, mcpServer);
674
+ }
675
+ });
676
+ transport.onclose = () => {
677
+ if (transport.sessionId) this.sessionManager.deleteSession(transport.sessionId);
678
+ };
679
+ await mcpServer.connect(transport);
680
+ } else {
681
+ res.status(400).json({
682
+ jsonrpc: "2.0",
683
+ error: {
684
+ code: -32e3,
685
+ message: "Bad Request: No valid session ID provided"
686
+ },
687
+ id: null
688
+ });
689
+ return;
690
+ }
691
+ await transport.handleRequest(req, res, req.body);
692
+ }
693
+ async handleGetRequest(req, res) {
694
+ const sessionId = req.headers["mcp-session-id"];
695
+ if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
696
+ res.status(400).send("Invalid or missing session ID");
697
+ return;
698
+ }
699
+ await this.sessionManager.getSession(sessionId).transport.handleRequest(req, res);
700
+ }
701
+ async handleDeleteRequest(req, res) {
702
+ const sessionId = req.headers["mcp-session-id"];
703
+ if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
704
+ res.status(400).send("Invalid or missing session ID");
705
+ return;
706
+ }
707
+ await this.sessionManager.getSession(sessionId).transport.handleRequest(req, res);
708
+ this.sessionManager.deleteSession(sessionId);
709
+ }
710
+ async start() {
711
+ return new Promise((resolve$1, reject) => {
712
+ try {
713
+ this.server = this.app.listen(this.config.port, this.config.host, () => {
714
+ process.stderr.write(`imagine-mcp MCP server started on http://${this.config.host}:${this.config.port}/mcp\n`);
715
+ process.stderr.write(`Health check: http://${this.config.host}:${this.config.port}/health\n`);
716
+ resolve$1();
717
+ });
718
+ this.server.on("error", (error) => {
719
+ reject(error);
720
+ });
721
+ } catch (error) {
722
+ reject(error);
723
+ }
724
+ });
725
+ }
726
+ async stop() {
727
+ return new Promise((resolve$1, reject) => {
728
+ if (this.server) {
729
+ this.sessionManager.clear();
730
+ this.server.close((err) => {
731
+ if (err) reject(err);
732
+ else {
733
+ this.server = null;
734
+ resolve$1();
735
+ }
736
+ });
737
+ } else resolve$1();
738
+ });
739
+ }
740
+ getPort() {
741
+ return this.config.port;
742
+ }
743
+ getHost() {
744
+ return this.config.host;
745
+ }
746
+ };
747
+
748
+ //#endregion
749
+ //#region src/transports/sse.ts
750
+ /**
751
+ * Session manager for SSE transports
752
+ */
753
+ var SseSessionManager = class {
754
+ sessions = /* @__PURE__ */ new Map();
755
+ getSession(sessionId) {
756
+ return this.sessions.get(sessionId)?.transport;
757
+ }
758
+ setSession(sessionId, transport, server) {
759
+ this.sessions.set(sessionId, {
760
+ transport,
761
+ server
762
+ });
763
+ }
764
+ deleteSession(sessionId) {
765
+ const session = this.sessions.get(sessionId);
766
+ if (session) session.server.close();
767
+ this.sessions.delete(sessionId);
768
+ }
769
+ hasSession(sessionId) {
770
+ return this.sessions.has(sessionId);
771
+ }
772
+ clear() {
773
+ for (const session of this.sessions.values()) session.server.close();
774
+ this.sessions.clear();
775
+ }
776
+ };
777
+ /**
778
+ * SSE (Server-Sent Events) transport handler
779
+ * Legacy transport for backwards compatibility (protocol version 2024-11-05)
780
+ * Uses separate endpoints: /sse for SSE stream (GET) and /messages for client messages (POST)
781
+ */
782
+ var SseTransportHandler = class {
783
+ serverFactory;
784
+ app;
785
+ server = null;
786
+ sessionManager;
787
+ config;
788
+ constructor(serverFactory, config$1) {
789
+ this.serverFactory = typeof serverFactory === "function" ? serverFactory : () => serverFactory;
790
+ this.app = (0, express.default)();
791
+ this.sessionManager = new SseSessionManager();
792
+ this.config = {
793
+ mode: config$1.mode,
794
+ port: config$1.port ?? 3e3,
795
+ host: config$1.host ?? "localhost"
796
+ };
797
+ this.setupMiddleware();
798
+ this.setupRoutes();
799
+ }
800
+ setupMiddleware() {
801
+ this.app.use(express.default.json());
802
+ }
803
+ setupRoutes() {
804
+ this.app.get("/sse", async (req, res) => {
805
+ await this.handleSseConnection(req, res);
806
+ });
807
+ this.app.post("/messages", async (req, res) => {
808
+ await this.handlePostMessage(req, res);
809
+ });
810
+ this.app.get("/health", (_req, res) => {
811
+ res.json({
812
+ status: "ok",
813
+ transport: "sse"
814
+ });
815
+ });
816
+ }
817
+ async handleSseConnection(_req, res) {
818
+ try {
819
+ const mcpServer = this.serverFactory();
820
+ const transport = new __modelcontextprotocol_sdk_server_sse_js.SSEServerTransport("/messages", res);
821
+ this.sessionManager.setSession(transport.sessionId, transport, mcpServer);
822
+ res.on("close", () => {
823
+ this.sessionManager.deleteSession(transport.sessionId);
824
+ });
825
+ await mcpServer.connect(transport);
826
+ process.stderr.write(`SSE session established: ${transport.sessionId}\n`);
827
+ } catch (error) {
828
+ process.stderr.write(`Error handling SSE connection: ${error instanceof Error ? error.message : String(error)}\n`);
829
+ if (!res.headersSent) res.status(500).send("Internal Server Error");
830
+ }
831
+ }
832
+ async handlePostMessage(req, res) {
833
+ const sessionId = req.query.sessionId;
834
+ if (!sessionId) {
835
+ res.status(400).send("Missing sessionId query parameter");
836
+ return;
837
+ }
838
+ const transport = this.sessionManager.getSession(sessionId);
839
+ if (!transport) {
840
+ res.status(404).send("No transport found for sessionId");
841
+ return;
842
+ }
843
+ try {
844
+ await transport.handlePostMessage(req, res, req.body);
845
+ } catch (error) {
846
+ process.stderr.write(`Error handling post message: ${error instanceof Error ? error.message : String(error)}\n`);
847
+ if (!res.headersSent) res.status(500).send("Internal Server Error");
848
+ }
849
+ }
850
+ async start() {
851
+ return new Promise((resolve$1, reject) => {
852
+ try {
853
+ this.server = this.app.listen(this.config.port, this.config.host, () => {
854
+ process.stderr.write(`imagine-mcp MCP server started with SSE transport on http://${this.config.host}:${this.config.port}\n`);
855
+ process.stderr.write(`SSE endpoint: http://${this.config.host}:${this.config.port}/sse\n`);
856
+ process.stderr.write(`Messages endpoint: http://${this.config.host}:${this.config.port}/messages\n`);
857
+ process.stderr.write(`Health check: http://${this.config.host}:${this.config.port}/health\n`);
858
+ resolve$1();
859
+ });
860
+ this.server.on("error", (error) => {
861
+ reject(error);
862
+ });
863
+ } catch (error) {
864
+ reject(error);
865
+ }
866
+ });
867
+ }
868
+ async stop() {
869
+ return new Promise((resolve$1, reject) => {
870
+ if (this.server) {
871
+ this.sessionManager.clear();
872
+ this.server.close((err) => {
873
+ if (err) reject(err);
874
+ else {
875
+ this.server = null;
876
+ resolve$1();
877
+ }
878
+ });
879
+ } else resolve$1();
880
+ });
881
+ }
882
+ getPort() {
883
+ return this.config.port;
884
+ }
885
+ getHost() {
886
+ return this.config.host;
887
+ }
888
+ };
889
+
890
+ //#endregion
891
+ //#region src/transports/stdio.ts
892
+ /**
893
+ * Stdio transport handler for MCP server
894
+ * Used for command-line and direct integrations
895
+ */
896
+ var StdioTransportHandler = class {
897
+ server;
898
+ transport = null;
899
+ constructor(server) {
900
+ this.server = server;
901
+ }
902
+ async start() {
903
+ this.transport = new __modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
904
+ await this.server.connect(this.transport);
905
+ process.stderr.write("imagine-mcp MCP server started on stdio\n");
906
+ }
907
+ async stop() {
908
+ if (this.transport) {
909
+ await this.transport.close();
910
+ this.transport = null;
911
+ }
912
+ }
913
+ };
914
+
915
+ //#endregion
916
+ Object.defineProperty(exports, 'HttpTransportHandler', {
917
+ enumerable: true,
918
+ get: function () {
919
+ return HttpTransportHandler;
920
+ }
921
+ });
922
+ Object.defineProperty(exports, 'ReadImageTool', {
923
+ enumerable: true,
924
+ get: function () {
925
+ return ReadImageTool;
926
+ }
927
+ });
928
+ Object.defineProperty(exports, 'SseTransportHandler', {
929
+ enumerable: true,
930
+ get: function () {
931
+ return SseTransportHandler;
932
+ }
933
+ });
934
+ Object.defineProperty(exports, 'StdioTransportHandler', {
935
+ enumerable: true,
936
+ get: function () {
937
+ return StdioTransportHandler;
938
+ }
939
+ });
940
+ Object.defineProperty(exports, 'UnsplashSearchTool', {
941
+ enumerable: true,
942
+ get: function () {
943
+ return UnsplashSearchTool;
944
+ }
945
+ });
946
+ Object.defineProperty(exports, '__toESM', {
947
+ enumerable: true,
948
+ get: function () {
949
+ return __toESM;
950
+ }
951
+ });
952
+ Object.defineProperty(exports, 'createServer', {
953
+ enumerable: true,
954
+ get: function () {
955
+ return createServer;
956
+ }
957
+ });