@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.
- package/LICENSE +52 -0
- package/README.md +144 -0
- package/dist/cli.cjs +129 -0
- package/dist/cli.d.cts +43 -0
- package/dist/cli.d.mts +43 -0
- package/dist/cli.mjs +125 -0
- package/dist/commands-BlRAon0l.cjs +83 -0
- package/dist/commands-c927CRZc.mjs +83 -0
- package/dist/index.cjs +8 -0
- package/dist/index.d.cts +150 -0
- package/dist/index.d.mts +150 -0
- package/dist/index.mjs +3 -0
- package/dist/stdio-CuIrQe3m.mjs +891 -0
- package/dist/stdio-D7OcvsZd.cjs +957 -0
- package/package.json +59 -0
|
@@ -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
|
+
});
|