@vint.tri/report_gen_mcp 1.5.17 → 1.5.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -374
- package/dist/tools/simpleReport.d.ts +19 -0
- package/dist/tools/simpleReport.d.ts.map +1 -0
- package/dist/tools/simpleReport.js +62 -0
- package/dist/tools/testReport.d.ts +12 -0
- package/dist/tools/testReport.d.ts.map +1 -0
- package/dist/tools/testReport.js +76 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,19 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
* Generate an image from a text prompt using AI
|
|
4
|
-
* @param prompt Text prompt for image generation (must be in English)
|
|
5
|
-
* @param options Additional options for image generation
|
|
6
|
-
* @returns Promise<{ filePath: string, fileUrl: string }> Object containing file path and URL of the saved image
|
|
7
|
-
*/
|
|
8
|
-
export declare function generateImage(prompt: string, options?: {
|
|
9
|
-
width?: number;
|
|
10
|
-
height?: number;
|
|
11
|
-
guidance_scale?: number;
|
|
12
|
-
negative_prompt?: string;
|
|
13
|
-
num_inference_steps?: number;
|
|
14
|
-
seed?: number;
|
|
15
|
-
}): Promise<{
|
|
16
|
-
filePath: string;
|
|
17
|
-
fileUrl: string;
|
|
18
|
-
}>;
|
|
2
|
+
export {};
|
|
19
3
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
CHANGED
|
@@ -4,12 +4,10 @@ import { program } from 'commander';
|
|
|
4
4
|
import { generateReport } from './utils/reportGenerator.js';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import fs from 'fs-extra';
|
|
7
|
-
import os from 'os';
|
|
8
7
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
9
8
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
10
|
-
import { z } from 'zod';
|
|
11
9
|
import { pathToFileURL } from 'url';
|
|
12
|
-
import {
|
|
10
|
+
import { simpleReportTool } from './tools/simpleReport.js';
|
|
13
11
|
// Check if we're running in stdio mode (no command-line arguments)
|
|
14
12
|
const isStdioMode = process.argv.length === 2;
|
|
15
13
|
// For CLI and HTTP API modes, check for mandatory REPORTS_DIR environment variable
|
|
@@ -103,339 +101,17 @@ if (process.argv.length === 2) {
|
|
|
103
101
|
// No command specified, run in stdio mode using MCP SDK
|
|
104
102
|
const mcpServer = new McpServer({
|
|
105
103
|
name: "report_gen_mcp",
|
|
106
|
-
version: "1.5.
|
|
104
|
+
version: "1.5.18"
|
|
107
105
|
}, {
|
|
108
106
|
// Disable health check to prevent automatic calls
|
|
109
107
|
capabilities: {
|
|
110
108
|
tools: {}
|
|
111
109
|
}
|
|
112
110
|
});
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
document: z.string().describe("Markdown document with element placeholders [[chart:id]] or [[image:id]]"),
|
|
118
|
-
elements: z.record(z.union([
|
|
119
|
-
z.object({
|
|
120
|
-
type: z.enum(["bar", "line", "pie", "doughnut", "radar", "polarArea"]),
|
|
121
|
-
config: z.object({
|
|
122
|
-
labels: z.array(z.string()),
|
|
123
|
-
datasets: z.array(z.object({
|
|
124
|
-
label: z.string().optional(),
|
|
125
|
-
data: z.array(z.number()),
|
|
126
|
-
backgroundColor: z.array(z.string()).optional(),
|
|
127
|
-
borderColor: z.array(z.string()).optional(),
|
|
128
|
-
fill: z.boolean().optional(),
|
|
129
|
-
})),
|
|
130
|
-
options: z.object({
|
|
131
|
-
title: z.string().optional(),
|
|
132
|
-
}).optional(),
|
|
133
|
-
}),
|
|
134
|
-
}).describe("Chart configuration"),
|
|
135
|
-
z.object({
|
|
136
|
-
type: z.literal("pollinations"),
|
|
137
|
-
config: z.object({
|
|
138
|
-
prompt: z.string().describe("Text description of the image to generate"),
|
|
139
|
-
width: z.number().optional().default(512).describe("Width of the image in pixels"),
|
|
140
|
-
height: z.number().optional().default(512).describe("Height of the image in pixels"),
|
|
141
|
-
model: z.string().optional().default("flux").describe("Model to use for image generation"),
|
|
142
|
-
seed: z.number().optional().describe("Seed for reproducible image generation"),
|
|
143
|
-
nologo: z.boolean().optional().default(true).describe("Remove logo from the image"),
|
|
144
|
-
enhance: z.boolean().optional().default(true).describe("Enhance image quality"),
|
|
145
|
-
}),
|
|
146
|
-
}).describe("Pollinations.ai generated image configuration"),
|
|
147
|
-
z.object({
|
|
148
|
-
type: z.literal("url"),
|
|
149
|
-
config: z.object({
|
|
150
|
-
url: z.string().url().describe("URL of the image"),
|
|
151
|
-
alt: z.string().optional().describe("Alternative text for the image"),
|
|
152
|
-
width: z.number().optional().describe("Width of the image in pixels"),
|
|
153
|
-
height: z.number().optional().describe("Height of the image in pixels"),
|
|
154
|
-
}),
|
|
155
|
-
}).describe("Image from URL configuration"),
|
|
156
|
-
z.object({
|
|
157
|
-
type: z.literal("image"),
|
|
158
|
-
config: z.object({
|
|
159
|
-
url: z.string().url().describe("File URL of the image"),
|
|
160
|
-
alt: z.string().optional().describe("Alternative text for the image"),
|
|
161
|
-
width: z.number().optional().describe("Width of the image in pixels"),
|
|
162
|
-
height: z.number().optional().describe("Height of the image in pixels"),
|
|
163
|
-
}),
|
|
164
|
-
}).describe("Local image file configuration"),
|
|
165
|
-
])).describe("Report elements (charts and images) mapped by ID"),
|
|
166
|
-
outputFile: z.string().optional().describe("Output HTML file path"),
|
|
167
|
-
tempDirectory: z.string().optional().describe("Temporary directory for file storage (optional, will use REPORTS_DIR environment variable if set)"),
|
|
168
|
-
},
|
|
169
|
-
}, async (params) => {
|
|
170
|
-
// Handle case where arguments might be sent as a JSON string by Claude desktop
|
|
171
|
-
let processedParams = params;
|
|
172
|
-
if (typeof params === 'string') {
|
|
173
|
-
try {
|
|
174
|
-
processedParams = JSON.parse(params);
|
|
175
|
-
}
|
|
176
|
-
catch (parseError) {
|
|
177
|
-
throw new Error('Invalid JSON string in arguments');
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
if (processedParams.arguments && typeof processedParams.arguments === 'string') {
|
|
181
|
-
try {
|
|
182
|
-
processedParams = JSON.parse(processedParams.arguments);
|
|
183
|
-
}
|
|
184
|
-
catch (parseError) {
|
|
185
|
-
throw new Error('Invalid JSON string in arguments');
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
else if (processedParams.arguments && typeof processedParams.arguments === 'object') {
|
|
189
|
-
processedParams = processedParams.arguments;
|
|
190
|
-
}
|
|
191
|
-
// Extract parameters correctly, ensuring outputFile is not nested within elements
|
|
192
|
-
const { document, elements, charts, outputFile = 'report.html', tempDirectory } = processedParams;
|
|
193
|
-
// Поддержка обратной совместимости: если переданы charts, используем их как elements
|
|
194
|
-
const reportElements = elements || charts || {};
|
|
195
|
-
// Determine the output directory:
|
|
196
|
-
// 1. Use REPORTS_DIR environment variable if set
|
|
197
|
-
// 2. Fall back to tempDirectory parameter if provided
|
|
198
|
-
// 3. Default to system temp directory if neither is available
|
|
199
|
-
let outputDir;
|
|
200
|
-
if (process.env.REPORTS_DIR) {
|
|
201
|
-
outputDir = process.env.REPORTS_DIR;
|
|
202
|
-
// Ensure the reports directory exists
|
|
203
|
-
try {
|
|
204
|
-
fs.ensureDirSync(outputDir);
|
|
205
|
-
}
|
|
206
|
-
catch (error) {
|
|
207
|
-
throw new Error(`Cannot create or access the reports directory: ${outputDir}`);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
else if (tempDirectory) {
|
|
211
|
-
outputDir = tempDirectory;
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
outputDir = os.tmpdir();
|
|
215
|
-
}
|
|
216
|
-
const outputPath = path.resolve(outputDir, outputFile);
|
|
217
|
-
try {
|
|
218
|
-
const result = await generateReport(document, reportElements, outputPath);
|
|
219
|
-
// Generate proper file URL
|
|
220
|
-
const fileUrl = pathToFileURL(outputPath).href;
|
|
221
|
-
// Read the file content
|
|
222
|
-
const fileContent = await fs.readFile(outputPath, 'utf8');
|
|
223
|
-
// Create a comprehensive result object
|
|
224
|
-
const comprehensiveResult = {
|
|
225
|
-
success: true,
|
|
226
|
-
message: "Report generated successfully",
|
|
227
|
-
filePath: outputPath,
|
|
228
|
-
fileUrl: fileUrl,
|
|
229
|
-
fileContent: fileContent
|
|
230
|
-
};
|
|
231
|
-
// Return comprehensive information for neural network to use
|
|
232
|
-
// Neural network should present to user: file path, web link, and file content
|
|
233
|
-
return {
|
|
234
|
-
content: [
|
|
235
|
-
{
|
|
236
|
-
type: "text",
|
|
237
|
-
text: `Report generated successfully.
|
|
238
|
-
|
|
239
|
-
FOR THE NEURAL NETWORK: Please present the following information to the user:
|
|
240
|
-
1. Path to file: ${outputPath}
|
|
241
|
-
2. Web browser link: ${fileUrl}
|
|
242
|
-
3. Web link 2: http://localhost:3000/${outputFile}
|
|
243
|
-
4. File content:
|
|
244
|
-
${fileContent}`
|
|
245
|
-
}
|
|
246
|
-
]
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
catch (error) {
|
|
250
|
-
throw error;
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
// Register a new tool for editing existing reports
|
|
254
|
-
mcpServer.registerTool("edit-report", {
|
|
255
|
-
description: "Edit an existing HTML report by adding or removing content",
|
|
256
|
-
inputSchema: {
|
|
257
|
-
filePath: z.string().describe("Path to the existing HTML report file"),
|
|
258
|
-
operation: z.enum(["add", "remove", "replace"]).describe("Operation to perform: add content, remove content, or replace content"),
|
|
259
|
-
contentToAdd: z.string().optional().describe("Content to add to the report (for 'add' operation)"),
|
|
260
|
-
selector: z.string().optional().describe("CSS selector for element to modify (for 'add', 'remove', or 'replace' operations)"),
|
|
261
|
-
contentToReplace: z.string().optional().describe("Content to replace the selected element with (for 'replace' operation)"),
|
|
262
|
-
position: z.enum(["beforebegin", "afterbegin", "beforeend", "afterend"]).optional().describe("Position to insert new content relative to the selected element (for 'add' operation)"),
|
|
263
|
-
},
|
|
264
|
-
}, async (params) => {
|
|
265
|
-
// Handle case where arguments might be sent as a JSON string
|
|
266
|
-
let processedParams = params;
|
|
267
|
-
if (typeof params === 'string') {
|
|
268
|
-
try {
|
|
269
|
-
processedParams = JSON.parse(params);
|
|
270
|
-
}
|
|
271
|
-
catch (parseError) {
|
|
272
|
-
throw new Error('Invalid JSON string in arguments');
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
else if (params.arguments && typeof params.arguments === 'object') {
|
|
276
|
-
processedParams = params.arguments;
|
|
277
|
-
}
|
|
278
|
-
const { filePath, operation, contentToAdd, selector, contentToReplace, position } = processedParams;
|
|
279
|
-
try {
|
|
280
|
-
// Check if file exists
|
|
281
|
-
if (!await fs.pathExists(filePath)) {
|
|
282
|
-
throw new Error(`File not found: ${filePath}`);
|
|
283
|
-
}
|
|
284
|
-
// Read the existing file content
|
|
285
|
-
let fileContent = await fs.readFile(filePath, 'utf8');
|
|
286
|
-
// Perform the requested operation
|
|
287
|
-
switch (operation) {
|
|
288
|
-
case "add":
|
|
289
|
-
if (!contentToAdd || !selector || !position) {
|
|
290
|
-
throw new Error("contentToAdd, selector, and position are required for 'add' operation");
|
|
291
|
-
}
|
|
292
|
-
// Insert content at the specified position relative to the selected element
|
|
293
|
-
const addRegex = new RegExp(`(<${selector}[^>]*>)|(<\\/[^>]*${selector}>)`, 'i');
|
|
294
|
-
const addMatch = fileContent.match(addRegex);
|
|
295
|
-
if (addMatch) {
|
|
296
|
-
const matchIndex = addMatch.index;
|
|
297
|
-
const matchLength = addMatch[0].length;
|
|
298
|
-
switch (position) {
|
|
299
|
-
case "beforebegin":
|
|
300
|
-
fileContent = fileContent.slice(0, matchIndex) + contentToAdd + fileContent.slice(matchIndex);
|
|
301
|
-
break;
|
|
302
|
-
case "afterbegin":
|
|
303
|
-
fileContent = fileContent.slice(0, matchIndex + matchLength) + contentToAdd + fileContent.slice(matchIndex + matchLength);
|
|
304
|
-
break;
|
|
305
|
-
case "beforeend":
|
|
306
|
-
const closingTagIndex = fileContent.indexOf('</', matchIndex);
|
|
307
|
-
fileContent = fileContent.slice(0, closingTagIndex) + contentToAdd + fileContent.slice(closingTagIndex);
|
|
308
|
-
break;
|
|
309
|
-
case "afterend":
|
|
310
|
-
const endTagIndex = fileContent.indexOf('>', matchIndex) + 1;
|
|
311
|
-
fileContent = fileContent.slice(0, endTagIndex) + contentToAdd + fileContent.slice(endTagIndex);
|
|
312
|
-
break;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
throw new Error(`Element with selector '${selector}' not found`);
|
|
317
|
-
}
|
|
318
|
-
break;
|
|
319
|
-
case "remove":
|
|
320
|
-
if (!selector) {
|
|
321
|
-
throw new Error("selector is required for 'remove' operation");
|
|
322
|
-
}
|
|
323
|
-
// Remove the element with the specified selector
|
|
324
|
-
const removeRegex = new RegExp(`<${selector}[^>]*>[^]*?<\\/[^>]*${selector}[^>]*>|<${selector}[^>]*/?>`, 'gi');
|
|
325
|
-
fileContent = fileContent.replace(removeRegex, '');
|
|
326
|
-
break;
|
|
327
|
-
case "replace":
|
|
328
|
-
if (!selector || !contentToReplace) {
|
|
329
|
-
throw new Error("selector and contentToReplace are required for 'replace' operation");
|
|
330
|
-
}
|
|
331
|
-
// Replace the element with the specified selector
|
|
332
|
-
const replaceRegex = new RegExp(`<${selector}[^>]*>[^]*?<\\/[^>]*${selector}[^>]*>|<${selector}[^>]*/?>`, 'gi');
|
|
333
|
-
fileContent = fileContent.replace(replaceRegex, contentToReplace);
|
|
334
|
-
break;
|
|
335
|
-
default:
|
|
336
|
-
throw new Error(`Unsupported operation: ${operation}`);
|
|
337
|
-
}
|
|
338
|
-
// Write the modified content back to the file
|
|
339
|
-
await fs.writeFile(filePath, fileContent, 'utf8');
|
|
340
|
-
// Generate proper file URL
|
|
341
|
-
const fileUrl = pathToFileURL(filePath).href;
|
|
342
|
-
return {
|
|
343
|
-
content: [
|
|
344
|
-
{
|
|
345
|
-
type: "text",
|
|
346
|
-
text: `Report edited successfully.
|
|
347
|
-
|
|
348
|
-
FOR THE NEURAL NETWORK: Please present the following information to the user:
|
|
349
|
-
1. Path to file: ${filePath}
|
|
350
|
-
2. Web browser link: ${fileUrl}
|
|
351
|
-
3. Operation performed: ${operation}`
|
|
352
|
-
}
|
|
353
|
-
]
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
catch (error) {
|
|
357
|
-
throw new Error(`Error editing report: ${error.message}`);
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
// Register the generate-image tool
|
|
361
|
-
mcpServer.registerTool("generate-image", {
|
|
362
|
-
description: "Generate an image from a text prompt using AI",
|
|
363
|
-
inputSchema: {
|
|
364
|
-
prompt: z.string().describe("Text prompt for image generation (must be in English)"),
|
|
365
|
-
width: z.number().optional().describe("Width of the image (128-2048 pixels)"),
|
|
366
|
-
height: z.number().optional().describe("Height of the image (128-2048 pixels)"),
|
|
367
|
-
guidance_scale: z.number().optional().describe("Strength of prompt following (1.0-20.0)"),
|
|
368
|
-
negative_prompt: z.string().optional().describe("Negative prompt (what NOT to include in the image)"),
|
|
369
|
-
num_inference_steps: z.number().optional().describe("Number of generation steps (1-50)"),
|
|
370
|
-
seed: z.number().optional().describe("Seed for reproducibility (0 = random)")
|
|
371
|
-
},
|
|
372
|
-
}, async (params) => {
|
|
373
|
-
try {
|
|
374
|
-
// Handle case where arguments might be sent as a JSON string
|
|
375
|
-
let processedParams = params;
|
|
376
|
-
if (typeof params === 'string') {
|
|
377
|
-
try {
|
|
378
|
-
processedParams = JSON.parse(params);
|
|
379
|
-
}
|
|
380
|
-
catch (parseError) {
|
|
381
|
-
throw new Error('Invalid JSON string in arguments');
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
else if (params.arguments && typeof params.arguments === 'object') {
|
|
385
|
-
processedParams = params.arguments;
|
|
386
|
-
}
|
|
387
|
-
const { prompt, width, height, guidance_scale, negative_prompt, num_inference_steps, seed } = processedParams;
|
|
388
|
-
if (!prompt) {
|
|
389
|
-
throw new Error("Parameter 'prompt' is required");
|
|
390
|
-
}
|
|
391
|
-
// Prepare options for generateImage function. Parameters are already numbers due to schema validation.
|
|
392
|
-
const options = {};
|
|
393
|
-
if (width !== undefined)
|
|
394
|
-
options.width = width;
|
|
395
|
-
if (height !== undefined)
|
|
396
|
-
options.height = height;
|
|
397
|
-
if (guidance_scale !== undefined)
|
|
398
|
-
options.guidance_scale = guidance_scale;
|
|
399
|
-
if (negative_prompt !== undefined)
|
|
400
|
-
options.negative_prompt = negative_prompt;
|
|
401
|
-
if (num_inference_steps !== undefined)
|
|
402
|
-
options.num_inference_steps = num_inference_steps;
|
|
403
|
-
if (seed !== undefined)
|
|
404
|
-
options.seed = seed;
|
|
405
|
-
// Generate the image
|
|
406
|
-
const imageResult = await generateImage(prompt, options);
|
|
407
|
-
// Extract base64 data from the saved file
|
|
408
|
-
const imageData = await fs.readFile(imageResult.filePath);
|
|
409
|
-
const base64Data = imageData.toString('base64');
|
|
410
|
-
// Return file paths and URLs
|
|
411
|
-
const reportServerPort = 2000;
|
|
412
|
-
const httpUrl = `http://localhost:${reportServerPort}/${path.basename(imageResult.filePath)}`;
|
|
413
|
-
return {
|
|
414
|
-
content: [
|
|
415
|
-
{
|
|
416
|
-
type: "text",
|
|
417
|
-
text: `Image generated successfully.
|
|
418
|
-
|
|
419
|
-
FOR THE NEURAL NETWORK: Please present the following information to the user:
|
|
420
|
-
1. Path to file: ${imageResult.filePath}
|
|
421
|
-
2. Web browser link: ${imageResult.fileUrl}
|
|
422
|
-
3. Web link: ${httpUrl}`
|
|
423
|
-
}
|
|
424
|
-
]
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
catch (error) {
|
|
428
|
-
// Return error as text content
|
|
429
|
-
return {
|
|
430
|
-
content: [
|
|
431
|
-
{
|
|
432
|
-
type: "text",
|
|
433
|
-
text: `❌ Error generating image: ${error.message}`
|
|
434
|
-
}
|
|
435
|
-
]
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
});
|
|
111
|
+
mcpServer.registerTool(simpleReportTool.name, {
|
|
112
|
+
description: simpleReportTool.description,
|
|
113
|
+
inputSchema: simpleReportTool.inputSchema,
|
|
114
|
+
}, simpleReportTool.action);
|
|
439
115
|
async function main() {
|
|
440
116
|
const transport = new StdioServerTransport();
|
|
441
117
|
await mcpServer.connect(transport);
|
|
@@ -450,47 +126,3 @@ else {
|
|
|
450
126
|
// Run commander program when arguments are provided
|
|
451
127
|
program.parse();
|
|
452
128
|
}
|
|
453
|
-
// Create a singleton instance of ImageGenerator for the generateImage function
|
|
454
|
-
const imageGenerator = new ImageGenerator();
|
|
455
|
-
// Start the report server in non-stdio mode as well
|
|
456
|
-
if (!isStdioMode && reportsDir) {
|
|
457
|
-
const reportServer = express();
|
|
458
|
-
const reportServerPort = 2000;
|
|
459
|
-
reportServer.use(express.static(reportsDir));
|
|
460
|
-
reportServer.listen(reportServerPort, () => {
|
|
461
|
-
console.log(`Report server running at http://localhost:${reportServerPort}`);
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* Generate an image from a text prompt using AI
|
|
466
|
-
* @param prompt Text prompt for image generation (must be in English)
|
|
467
|
-
* @param options Additional options for image generation
|
|
468
|
-
* @returns Promise<{ filePath: string, fileUrl: string }> Object containing file path and URL of the saved image
|
|
469
|
-
*/
|
|
470
|
-
export async function generateImage(prompt, options) {
|
|
471
|
-
// Generate the image data URI
|
|
472
|
-
const imageDataUri = await imageGenerator.generateImage(prompt, options);
|
|
473
|
-
// Extract base64 data without prefix
|
|
474
|
-
let base64Data = imageDataUri;
|
|
475
|
-
if (imageDataUri.startsWith("data:image/jpeg;base64,")) {
|
|
476
|
-
base64Data = imageDataUri.substring("data:image/jpeg;base64,".length);
|
|
477
|
-
}
|
|
478
|
-
// Save image to home folder
|
|
479
|
-
const homeDir = os.homedir();
|
|
480
|
-
const imagesDir = path.join(homeDir, 'generated_images');
|
|
481
|
-
await fs.ensureDir(imagesDir);
|
|
482
|
-
// Create unique filename
|
|
483
|
-
const timestamp = Date.now();
|
|
484
|
-
const filename = `generated-image-${timestamp}.jpeg`;
|
|
485
|
-
const outputPath = path.join(imagesDir, filename);
|
|
486
|
-
// Save image to file
|
|
487
|
-
const imageBuffer = Buffer.from(base64Data, 'base64');
|
|
488
|
-
await fs.writeFile(outputPath, imageBuffer);
|
|
489
|
-
// Create file URL
|
|
490
|
-
const fileUrl = pathToFileURL(outputPath).href;
|
|
491
|
-
// Return both file path and URL
|
|
492
|
-
return {
|
|
493
|
-
filePath: outputPath,
|
|
494
|
-
fileUrl: fileUrl
|
|
495
|
-
};
|
|
496
|
-
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const simpleReportTool: {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: {
|
|
6
|
+
document: z.ZodString;
|
|
7
|
+
outputFile: z.ZodOptional<z.ZodString>;
|
|
8
|
+
};
|
|
9
|
+
action: (params: any) => Promise<{
|
|
10
|
+
content: {
|
|
11
|
+
type: "resource";
|
|
12
|
+
resource: {
|
|
13
|
+
uri: string;
|
|
14
|
+
text: string;
|
|
15
|
+
};
|
|
16
|
+
}[];
|
|
17
|
+
}>;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=simpleReport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simpleReport.d.ts","sourceRoot":"","sources":["../../src/tools/simpleReport.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,gBAAgB;;;;;;;qBAOJ,GAAG;iBAwCG;YAAE,IAAI,EAAE,UAAU,CAAC;YAAC,QAAQ,EAAE;gBAAE,GAAG,EAAE,MAAM,CAAC;gBAAC,IAAI,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,EAAE;;CAa7F,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { generateReport } from '../utils/reportGenerator.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { pathToFileURL } from 'url';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
export const simpleReportTool = {
|
|
6
|
+
name: "generate-report",
|
|
7
|
+
description: "Generate an HTML report from a single string of Markdown content with embedded image and chart tags.",
|
|
8
|
+
inputSchema: {
|
|
9
|
+
document: z.string().describe("The full content of the report in Markdown format, with `<img>` and `<canvas>` tags for images and charts."),
|
|
10
|
+
outputFile: z.string().optional().describe("The name of the output HTML file."),
|
|
11
|
+
},
|
|
12
|
+
action: async (params) => {
|
|
13
|
+
let { document, outputFile } = params;
|
|
14
|
+
const elements = {};
|
|
15
|
+
// Regex to find canvas tags and extract id and data-chart
|
|
16
|
+
const canvasRegex = /<canvas\s+id="([^"]+)"\s+data-chart='([^']*)'><\/canvas>/g;
|
|
17
|
+
document = document.replace(canvasRegex, (match, id, chartConfigString) => {
|
|
18
|
+
try {
|
|
19
|
+
const chartConfig = JSON.parse(chartConfigString);
|
|
20
|
+
elements[id] = chartConfig;
|
|
21
|
+
return `[[chart:${id}]]`;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error(`Error parsing chart config for id ${id}:`, error);
|
|
25
|
+
// Keep the original tag if parsing fails to avoid losing content
|
|
26
|
+
return match;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
// Regex to find img tags and extract id and data-image
|
|
30
|
+
const imgRegex = /<img\s+id="([^"]+)"\s+data-image='([^']*)'\/?>/g;
|
|
31
|
+
document = document.replace(imgRegex, (match, id, imageConfigString) => {
|
|
32
|
+
try {
|
|
33
|
+
const imageConfig = JSON.parse(imageConfigString);
|
|
34
|
+
elements[id] = imageConfig;
|
|
35
|
+
return `[[image:${id}]]`;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.error(`Error parsing image config for id ${id}:`, error);
|
|
39
|
+
// Keep the original tag if parsing fails
|
|
40
|
+
return match;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
if (!outputFile) {
|
|
44
|
+
outputFile = `report-${Date.now()}.html`;
|
|
45
|
+
}
|
|
46
|
+
const result = await generateReport(document, elements, outputFile);
|
|
47
|
+
const fileUrl = pathToFileURL(result.filePath).href;
|
|
48
|
+
const fileContent = await fs.readFile(result.filePath, 'utf8');
|
|
49
|
+
const response = {
|
|
50
|
+
content: [
|
|
51
|
+
{
|
|
52
|
+
type: 'resource',
|
|
53
|
+
resource: {
|
|
54
|
+
uri: fileUrl,
|
|
55
|
+
text: fileContent,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
return response;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testReport.d.ts","sourceRoot":"","sources":["../../src/tools/testReport.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,cAAc;;;;;;;;;;CAyE1B,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { generateReport } from '../utils/reportGenerator.js';
|
|
2
|
+
import { pathToFileURL } from 'url';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
export const testReportTool = {
|
|
5
|
+
name: "test-report",
|
|
6
|
+
description: "Generate a hardcoded test report to verify that the MCP server is working correctly.",
|
|
7
|
+
inputSchema: {},
|
|
8
|
+
action: async () => {
|
|
9
|
+
const reportContent = `# Test Report
|
|
10
|
+
|
|
11
|
+
This is a test report.
|
|
12
|
+
|
|
13
|
+
## Image
|
|
14
|
+
|
|
15
|
+
<img src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" alt="Google Logo" />
|
|
16
|
+
|
|
17
|
+
## Chart
|
|
18
|
+
|
|
19
|
+
<canvas id="myChart"></canvas>
|
|
20
|
+
<script>
|
|
21
|
+
const ctx = document.getElementById('myChart').getContext('2d');
|
|
22
|
+
new Chart(ctx, {
|
|
23
|
+
type: 'bar',
|
|
24
|
+
data: {
|
|
25
|
+
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
|
|
26
|
+
datasets: [{
|
|
27
|
+
label: '# of Votes',
|
|
28
|
+
data: [12, 19, 3, 5, 2, 3],
|
|
29
|
+
backgroundColor: [
|
|
30
|
+
'rgba(255, 99, 132, 0.2)',
|
|
31
|
+
'rgba(54, 162, 235, 0.2)',
|
|
32
|
+
'rgba(255, 206, 86, 0.2)',
|
|
33
|
+
'rgba(75, 192, 192, 0.2)',
|
|
34
|
+
'rgba(153, 102, 255, 0.2)',
|
|
35
|
+
'rgba(255, 159, 64, 0.2)'
|
|
36
|
+
],
|
|
37
|
+
borderColor: [
|
|
38
|
+
'rgba(255, 99, 132, 1)',
|
|
39
|
+
'rgba(54, 162, 235, 1)',
|
|
40
|
+
'rgba(255, 206, 86, 1)',
|
|
41
|
+
'rgba(75, 192, 192, 1)',
|
|
42
|
+
'rgba(153, 102, 255, 1)',
|
|
43
|
+
'rgba(255, 159, 64, 1)'
|
|
44
|
+
],
|
|
45
|
+
borderWidth: 1
|
|
46
|
+
}]
|
|
47
|
+
},
|
|
48
|
+
options: {
|
|
49
|
+
scales: {
|
|
50
|
+
y: {
|
|
51
|
+
beginAtZero: true
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
</script>
|
|
57
|
+
`;
|
|
58
|
+
const result = await generateReport(reportContent, {}, "test-report.html");
|
|
59
|
+
const fileUrl = pathToFileURL(result.filePath).href;
|
|
60
|
+
const fileContent = await fs.readFile(result.filePath, 'utf8');
|
|
61
|
+
return {
|
|
62
|
+
content: [
|
|
63
|
+
{
|
|
64
|
+
type: "text",
|
|
65
|
+
text: `Test report generated successfully.
|
|
66
|
+
|
|
67
|
+
FOR THE NEURAL NETWORK: Please present the following information to the user:
|
|
68
|
+
1. Path to file: ${result.filePath}
|
|
69
|
+
2. Web browser link: ${fileUrl}
|
|
70
|
+
3. File content:
|
|
71
|
+
${fileContent}`
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
};
|