@vint.tri/report_gen_mcp 1.1.0 → 1.1.1

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/FIX_SUMMARY.md ADDED
@@ -0,0 +1,103 @@
1
+ # Report Generation Issue Fix Summary
2
+
3
+ ## Problem
4
+ The report generation was failing with the following error:
5
+ ```
6
+ Error calling tool generate-report: Error: Error invoking remote method 'mcp:call-tool': McpError: MCP error -32602: MCP error -32602: Invalid arguments for tool generate-report: [
7
+ {
8
+ "code": "invalid_type",
9
+ "expected": "array",
10
+ "received": "string",
11
+ "path": [
12
+ "charts",
13
+ "dollar_rate_chart",
14
+ "config",
15
+ "datasets",
16
+ 0,
17
+ "backgroundColor"
18
+ ],
19
+ "message": "Expected array, received string"
20
+ },
21
+ {
22
+ "code": "invalid_type",
23
+ "expected": "array",
24
+ "received": "string",
25
+ "path": [
26
+ "charts",
27
+ "dollar_rate_chart",
28
+ "config",
29
+ "datasets",
30
+ 0,
31
+ "borderColor"
32
+ ],
33
+ "message": "Expected array, received string"
34
+ }
35
+ ]
36
+ ```
37
+
38
+ ## Root Cause
39
+ The issue was in the chart configuration where `backgroundColor` and `borderColor` properties were provided as strings instead of arrays of strings. According to the schema validation in `src/charts/line.ts`, these properties are expected to be arrays:
40
+
41
+ ```typescript
42
+ export const lineSchema = z.object({
43
+ labels: z.array(z.string()),
44
+ datasets: z.array(z.object({
45
+ label: z.string(),
46
+ data: z.array(z.number()),
47
+ borderColor: z.array(z.string()).optional(),
48
+ fill: z.boolean().optional(),
49
+ })),
50
+ options: z.object({
51
+ title: z.string().optional(),
52
+ }).optional(),
53
+ });
54
+ ```
55
+
56
+ ## Solution
57
+ I fixed the configuration by:
58
+ 1. Removing the `backgroundColor` property entirely since it's not defined in the schema
59
+ 2. Changing `borderColor` from a string `"rgba(54, 162, 235, 1)"` to an array `["rgba(54, 162, 235, 1)"]`
60
+
61
+ ## Fixed Configuration
62
+ The corrected chart configuration is:
63
+
64
+ ```json
65
+ {
66
+ "dollar_rate_chart": {
67
+ "type": "line",
68
+ "config": {
69
+ "labels": [
70
+ "16.08.2025",
71
+ "19.08.2025",
72
+ "20.08.2025",
73
+ "21.08.2025",
74
+ "22.08.2025",
75
+ "23.08.2025"
76
+ ],
77
+ "datasets": [
78
+ {
79
+ "label": "Курс USD/RUB",
80
+ "data": [
81
+ 80.0224,
82
+ 80.4256,
83
+ 80.3466,
84
+ 80.1045,
85
+ 80.2548,
86
+ 80.7498
87
+ ],
88
+ "borderColor": [
89
+ "rgba(54, 162, 235, 1)"
90
+ ],
91
+ "fill": false
92
+ }
93
+ ],
94
+ "options": {
95
+ "title": "Динамика курса доллара США к рублю"
96
+ }
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ ## Verification
103
+ The report was successfully generated using the fixed configuration and can be found at `test_reports/dollar_rate_report.html`.
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # Report Generator MCP Tool
1
+ # Report Generator MCP Tool v1.1.1
2
2
 
3
- A powerful CLI tool for generating HTML reports with embedded charts, designed to work seamlessly with Claude Desktop as an MCP (Model Context Protocol) extension.
3
+ A powerful CLI tool for generating HTML reports with embedded interactive charts, designed to work seamlessly with Claude Desktop as an MCP (Model Context Protocol) extension.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - Generate professional HTML reports from Markdown documents
8
- - Embed customizable charts (bar, line, pie) directly in reports
8
+ - Embed customizable interactive charts (bar, line, pie, doughnut, radar, polarArea) directly in reports
9
9
  - Dual operation modes:
10
10
  - Command-line interface for direct usage
11
11
  - Stdio mode for Claude Desktop integration
@@ -131,7 +131,7 @@ Different regions show varying performance levels.
131
131
 
132
132
  The `charts` parameter is an object where each key is a chart ID that matches a placeholder in your document, and each value is a chart configuration object with:
133
133
 
134
- - `type`: The chart type ("bar", "line", or "pie")
134
+ - `type`: The chart type ("bar", "line", "pie", "doughnut", "radar", or "polarArea")
135
135
  - `config`: The Chart.js configuration for that chart
136
136
 
137
137
  #### Required Chart Data Structure
@@ -389,7 +389,7 @@ npm test
389
389
  **Parameters:**
390
390
  - `document` (string): Markdown document with chart placeholders `[[chart:id]]`
391
391
  - `charts` (object): Chart configurations mapped by ID
392
- - `type` (string): Chart type ("bar", "line", or "pie")
392
+ - `type` (string): Chart type ("bar", "line", "pie", "doughnut", "radar", or "polarArea")
393
393
  - `config` (object): Chart.js configuration object
394
394
  - `outputFile` (string, optional): Output HTML file path (defaults to "report.html")
395
395
  - `tempDirectory` (string, **required for Claude Desktop**): Temporary directory for file storage. This parameter is required when using the tool with Claude Desktop to prevent read-only file system errors.
@@ -1,5 +1,4 @@
1
1
  import { z } from 'zod';
2
- import { ChartJSNodeCanvas } from 'chartjs-node-canvas';
3
2
  export const barSchema = z.object({
4
3
  labels: z.array(z.string()),
5
4
  datasets: z.array(z.object({
@@ -14,26 +13,38 @@ export const barSchema = z.object({
14
13
  }).optional(),
15
14
  });
16
15
  export async function renderBarChart(config) {
17
- const chartConfig = {
18
- type: 'bar',
19
- data: {
20
- labels: config.labels,
21
- datasets: config.datasets,
22
- },
23
- options: {
24
- ...config.options,
25
- plugins: {
26
- title: config.options?.title ? {
27
- display: true,
28
- text: config.options.title
29
- } : undefined
30
- }
31
- },
16
+ // Generate a unique ID for the chart container
17
+ const chartId = `chart_${Math.random().toString(36).substr(2, 9)}`;
18
+ // Prepare the chart data as JSON
19
+ const chartData = {
20
+ labels: config.labels,
21
+ datasets: config.datasets,
22
+ };
23
+ // Prepare the chart options
24
+ const chartOptions = {
25
+ ...config.options,
26
+ plugins: {
27
+ title: config.options?.title ? {
28
+ display: true,
29
+ text: config.options.title
30
+ } : undefined
31
+ }
32
32
  };
33
- const width = 800;
34
- const height = 600;
35
- const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height });
36
- const buffer = await chartJSNodeCanvas.renderToBuffer(chartConfig);
37
- const base64Image = buffer.toString('base64');
38
- return `<img src="data:image/png;base64,${base64Image}" alt="Chart" />`;
33
+ // Return interactive Chart.js HTML
34
+ return `
35
+ <div>
36
+ <canvas id="${chartId}" width="800" height="600"></canvas>
37
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
38
+ <script>
39
+ document.addEventListener('DOMContentLoaded', function() {
40
+ const ctx = document.getElementById('${chartId}').getContext('2d');
41
+ new Chart(ctx, {
42
+ type: 'bar',
43
+ data: ${JSON.stringify(chartData)},
44
+ options: ${JSON.stringify(chartOptions)}
45
+ });
46
+ });
47
+ </script>
48
+ </div>
49
+ `.trim();
39
50
  }
@@ -0,0 +1,49 @@
1
+ import { z } from 'zod';
2
+ export const doughnutSchema = z.object({
3
+ labels: z.array(z.string()),
4
+ datasets: z.array(z.object({
5
+ data: z.array(z.number()),
6
+ backgroundColor: z.array(z.string()).optional(),
7
+ borderColor: z.array(z.string()).optional(),
8
+ })),
9
+ options: z.object({
10
+ title: z.string().optional(),
11
+ // Add more Chart.js options as needed
12
+ }).optional(),
13
+ });
14
+ export async function renderDoughnutChart(config) {
15
+ // Generate a unique ID for the chart container
16
+ const chartId = `chart_${Math.random().toString(36).substr(2, 9)}`;
17
+ // Prepare the chart data as JSON
18
+ const chartData = {
19
+ labels: config.labels,
20
+ datasets: config.datasets,
21
+ };
22
+ // Prepare the chart options
23
+ const chartOptions = {
24
+ ...config.options,
25
+ plugins: {
26
+ title: config.options?.title ? {
27
+ display: true,
28
+ text: config.options.title
29
+ } : undefined
30
+ }
31
+ };
32
+ // Return interactive Chart.js HTML
33
+ return `
34
+ <div>
35
+ <canvas id="${chartId}" width="800" height="600"></canvas>
36
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
37
+ <script>
38
+ document.addEventListener('DOMContentLoaded', function() {
39
+ const ctx = document.getElementById('${chartId}').getContext('2d');
40
+ new Chart(ctx, {
41
+ type: 'doughnut',
42
+ data: ${JSON.stringify(chartData)},
43
+ options: ${JSON.stringify(chartOptions)}
44
+ });
45
+ });
46
+ </script>
47
+ </div>
48
+ `.trim();
49
+ }
@@ -1,5 +1,4 @@
1
1
  import { z } from 'zod';
2
- import { ChartJSNodeCanvas } from 'chartjs-node-canvas';
3
2
  export const lineSchema = z.object({
4
3
  labels: z.array(z.string()),
5
4
  datasets: z.array(z.object({
@@ -14,26 +13,38 @@ export const lineSchema = z.object({
14
13
  }).optional(),
15
14
  });
16
15
  export async function renderLineChart(config) {
17
- const chartConfig = {
18
- type: 'line',
19
- data: {
20
- labels: config.labels,
21
- datasets: config.datasets,
22
- },
23
- options: {
24
- ...config.options,
25
- plugins: {
26
- title: config.options?.title ? {
27
- display: true,
28
- text: config.options.title
29
- } : undefined
30
- }
31
- },
16
+ // Generate a unique ID for the chart container
17
+ const chartId = `chart_${Math.random().toString(36).substr(2, 9)}`;
18
+ // Prepare the chart data as JSON
19
+ const chartData = {
20
+ labels: config.labels,
21
+ datasets: config.datasets,
22
+ };
23
+ // Prepare the chart options
24
+ const chartOptions = {
25
+ ...config.options,
26
+ plugins: {
27
+ title: config.options?.title ? {
28
+ display: true,
29
+ text: config.options.title
30
+ } : undefined
31
+ }
32
32
  };
33
- const width = 800;
34
- const height = 600;
35
- const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height });
36
- const buffer = await chartJSNodeCanvas.renderToBuffer(chartConfig);
37
- const base64Image = buffer.toString('base64');
38
- return `<img src="data:image/png;base64,${base64Image}" alt="Chart" />`;
33
+ // Return interactive Chart.js HTML
34
+ return `
35
+ <div>
36
+ <canvas id="${chartId}" width="800" height="600"></canvas>
37
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
38
+ <script>
39
+ document.addEventListener('DOMContentLoaded', function() {
40
+ const ctx = document.getElementById('${chartId}').getContext('2d');
41
+ new Chart(ctx, {
42
+ type: 'line',
43
+ data: ${JSON.stringify(chartData)},
44
+ options: ${JSON.stringify(chartOptions)}
45
+ });
46
+ });
47
+ </script>
48
+ </div>
49
+ `.trim();
39
50
  }
@@ -1,5 +1,4 @@
1
1
  import { z } from 'zod';
2
- import { ChartJSNodeCanvas } from 'chartjs-node-canvas';
3
2
  export const pieSchema = z.object({
4
3
  labels: z.array(z.string()),
5
4
  datasets: z.array(z.object({
@@ -13,26 +12,38 @@ export const pieSchema = z.object({
13
12
  }).optional(),
14
13
  });
15
14
  export async function renderPieChart(config) {
16
- const chartConfig = {
17
- type: 'pie',
18
- data: {
19
- labels: config.labels,
20
- datasets: config.datasets,
21
- },
22
- options: {
23
- ...config.options,
24
- plugins: {
25
- title: config.options?.title ? {
26
- display: true,
27
- text: config.options.title
28
- } : undefined
29
- }
30
- },
15
+ // Generate a unique ID for the chart container
16
+ const chartId = `chart_${Math.random().toString(36).substr(2, 9)}`;
17
+ // Prepare the chart data as JSON
18
+ const chartData = {
19
+ labels: config.labels,
20
+ datasets: config.datasets,
21
+ };
22
+ // Prepare the chart options
23
+ const chartOptions = {
24
+ ...config.options,
25
+ plugins: {
26
+ title: config.options?.title ? {
27
+ display: true,
28
+ text: config.options.title
29
+ } : undefined
30
+ }
31
31
  };
32
- const width = 800;
33
- const height = 600;
34
- const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height });
35
- const buffer = await chartJSNodeCanvas.renderToBuffer(chartConfig);
36
- const base64Image = buffer.toString('base64');
37
- return `<img src="data:image/png;base64,${base64Image}" alt="Chart" />`;
32
+ // Return interactive Chart.js HTML
33
+ return `
34
+ <div>
35
+ <canvas id="${chartId}" width="800" height="600"></canvas>
36
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
37
+ <script>
38
+ document.addEventListener('DOMContentLoaded', function() {
39
+ const ctx = document.getElementById('${chartId}').getContext('2d');
40
+ new Chart(ctx, {
41
+ type: 'pie',
42
+ data: ${JSON.stringify(chartData)},
43
+ options: ${JSON.stringify(chartOptions)}
44
+ });
45
+ });
46
+ </script>
47
+ </div>
48
+ `.trim();
38
49
  }
@@ -0,0 +1,49 @@
1
+ import { z } from 'zod';
2
+ export const polarAreaSchema = z.object({
3
+ labels: z.array(z.string()),
4
+ datasets: z.array(z.object({
5
+ data: z.array(z.number()),
6
+ backgroundColor: z.array(z.string()).optional(),
7
+ borderColor: z.array(z.string()).optional(),
8
+ })),
9
+ options: z.object({
10
+ title: z.string().optional(),
11
+ // Add more Chart.js options as needed
12
+ }).optional(),
13
+ });
14
+ export async function renderPolarAreaChart(config) {
15
+ // Generate a unique ID for the chart container
16
+ const chartId = `chart_${Math.random().toString(36).substr(2, 9)}`;
17
+ // Prepare the chart data as JSON
18
+ const chartData = {
19
+ labels: config.labels,
20
+ datasets: config.datasets,
21
+ };
22
+ // Prepare the chart options
23
+ const chartOptions = {
24
+ ...config.options,
25
+ plugins: {
26
+ title: config.options?.title ? {
27
+ display: true,
28
+ text: config.options.title
29
+ } : undefined
30
+ }
31
+ };
32
+ // Return interactive Chart.js HTML
33
+ return `
34
+ <div>
35
+ <canvas id="${chartId}" width="800" height="600"></canvas>
36
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
37
+ <script>
38
+ document.addEventListener('DOMContentLoaded', function() {
39
+ const ctx = document.getElementById('${chartId}').getContext('2d');
40
+ new Chart(ctx, {
41
+ type: 'polarArea',
42
+ data: ${JSON.stringify(chartData)},
43
+ options: ${JSON.stringify(chartOptions)}
44
+ });
45
+ });
46
+ </script>
47
+ </div>
48
+ `.trim();
49
+ }
@@ -0,0 +1,50 @@
1
+ import { z } from 'zod';
2
+ export const radarSchema = z.object({
3
+ labels: z.array(z.string()),
4
+ datasets: z.array(z.object({
5
+ label: z.string(),
6
+ data: z.array(z.number()),
7
+ backgroundColor: z.array(z.string()).optional(),
8
+ borderColor: z.array(z.string()).optional(),
9
+ })),
10
+ options: z.object({
11
+ title: z.string().optional(),
12
+ // Add more Chart.js options as needed
13
+ }).optional(),
14
+ });
15
+ export async function renderRadarChart(config) {
16
+ // Generate a unique ID for the chart container
17
+ const chartId = `chart_${Math.random().toString(36).substr(2, 9)}`;
18
+ // Prepare the chart data as JSON
19
+ const chartData = {
20
+ labels: config.labels,
21
+ datasets: config.datasets,
22
+ };
23
+ // Prepare the chart options
24
+ const chartOptions = {
25
+ ...config.options,
26
+ plugins: {
27
+ title: config.options?.title ? {
28
+ display: true,
29
+ text: config.options.title
30
+ } : undefined
31
+ }
32
+ };
33
+ // Return interactive Chart.js HTML
34
+ return `
35
+ <div>
36
+ <canvas id="${chartId}" width="800" height="600"></canvas>
37
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
38
+ <script>
39
+ document.addEventListener('DOMContentLoaded', function() {
40
+ const ctx = document.getElementById('${chartId}').getContext('2d');
41
+ new Chart(ctx, {
42
+ type: 'radar',
43
+ data: ${JSON.stringify(chartData)},
44
+ options: ${JSON.stringify(chartOptions)}
45
+ });
46
+ });
47
+ </script>
48
+ </div>
49
+ `.trim();
50
+ }
package/dist/index.js CHANGED
@@ -80,18 +80,24 @@ program
80
80
  const result = await generateReport(documentContent, charts, path.resolve(reportsDir, opts.output));
81
81
  // Generate proper file URL for CLI mode
82
82
  const fileUrl = pathToFileURL(result.filePath).href;
83
- const resultWithUrl = {
84
- ...result,
85
- fileUrl: fileUrl
83
+ // Read the file content
84
+ const fileContent = await fs.readFile(result.filePath, 'utf8');
85
+ // Create a comprehensive result object
86
+ const comprehensiveResult = {
87
+ success: true,
88
+ message: "Report generated successfully",
89
+ filePath: result.filePath,
90
+ fileUrl: fileUrl,
91
+ fileContent: fileContent
86
92
  };
87
- console.log(JSON.stringify(resultWithUrl));
93
+ console.log(JSON.stringify(comprehensiveResult, null, 2));
88
94
  });
89
95
  // Handle stdio mode for Claude Desktop integration
90
96
  if (process.argv.length === 2) {
91
97
  // No command specified, run in stdio mode using MCP SDK
92
98
  const mcpServer = new McpServer({
93
99
  name: "report_gen_mcp",
94
- version: "1.0.31",
100
+ version: "1.1.1",
95
101
  }, {
96
102
  // Disable health check to prevent automatic calls
97
103
  capabilities: {
@@ -104,7 +110,7 @@ if (process.argv.length === 2) {
104
110
  inputSchema: {
105
111
  document: z.string().describe("Markdown document with chart placeholders [[chart:id]]"),
106
112
  charts: z.record(z.object({
107
- type: z.enum(["bar", "line", "pie"]),
113
+ type: z.enum(["bar", "line", "pie", "doughnut", "radar", "polarArea"]),
108
114
  config: z.object({
109
115
  labels: z.array(z.string()),
110
116
  datasets: z.array(z.object({
@@ -163,16 +169,21 @@ if (process.argv.length === 2) {
163
169
  const result = await generateReport(document, charts, outputPath);
164
170
  // Generate proper file URL
165
171
  const fileUrl = pathToFileURL(outputPath).href;
172
+ // Read the file content
173
+ const fileContent = await fs.readFile(outputPath, 'utf8');
174
+ // Create a comprehensive result object
175
+ const comprehensiveResult = {
176
+ success: true,
177
+ message: "Report generated successfully",
178
+ filePath: outputPath,
179
+ fileUrl: fileUrl,
180
+ fileContent: fileContent
181
+ };
166
182
  return {
167
183
  content: [
168
184
  {
169
185
  type: "text",
170
- text: JSON.stringify({
171
- ...result,
172
- message: "Report generated successfully",
173
- filePath: outputPath,
174
- fileUrl: fileUrl
175
- })
186
+ text: JSON.stringify(comprehensiveResult, null, 2)
176
187
  }
177
188
  ]
178
189
  };
@@ -4,12 +4,16 @@ import { marked } from 'marked';
4
4
  import { barSchema, renderBarChart } from '../charts/bar.js';
5
5
  import { lineSchema, renderLineChart } from '../charts/line.js';
6
6
  import { pieSchema, renderPieChart } from '../charts/pie.js';
7
- // Add more imports for other chart types as needed
7
+ import { doughnutSchema, renderDoughnutChart } from '../charts/doughnut.js';
8
+ import { radarSchema, renderRadarChart } from '../charts/radar.js';
9
+ import { polarAreaSchema, renderPolarAreaChart } from '../charts/polarArea.js';
8
10
  const chartRenderers = {
9
11
  bar: { schema: barSchema, renderer: renderBarChart },
10
12
  line: { schema: lineSchema, renderer: renderLineChart },
11
13
  pie: { schema: pieSchema, renderer: renderPieChart },
12
- // Add more chart types here
14
+ doughnut: { schema: doughnutSchema, renderer: renderDoughnutChart },
15
+ radar: { schema: radarSchema, renderer: renderRadarChart },
16
+ polarArea: { schema: polarAreaSchema, renderer: renderPolarAreaChart },
13
17
  };
14
18
  export async function generateReport(document, charts, outputFile) {
15
19
  // Validate charts
@@ -19,27 +23,31 @@ export async function generateReport(document, charts, outputFile) {
19
23
  throw new Error(`Unsupported chart type: ${type}`);
20
24
  schema.parse(config);
21
25
  }
22
- // Render charts to HTML img tags
26
+ // Render charts to interactive HTML
23
27
  const renderedCharts = {};
24
28
  for (const [id, { type, config }] of Object.entries(charts)) {
25
- const htmlImg = await chartRenderers[type].renderer(config);
26
- renderedCharts[id] = htmlImg;
29
+ const htmlChart = await chartRenderers[type].renderer(config);
30
+ renderedCharts[id] = htmlChart;
27
31
  }
28
32
  // Replace placeholders in Markdown, e.g., [[chart:id]]
29
33
  let mdWithCharts = document;
30
- for (const [id, htmlImg] of Object.entries(renderedCharts)) {
31
- mdWithCharts = mdWithCharts.replace(new RegExp(`\\[\\[chart:${id}\\]\\]`, 'g'), htmlImg);
34
+ for (const [id, htmlChart] of Object.entries(renderedCharts)) {
35
+ mdWithCharts = mdWithCharts.replace(new RegExp(`\\[\\[chart:${id}\\]\\]`, 'g'), htmlChart);
32
36
  }
33
37
  // Convert Markdown to HTML
34
38
  const htmlContent = await marked(mdWithCharts);
35
- // Wrap in full HTML template
39
+ // Wrap in full HTML template with Chart.js library
36
40
  const template = `
37
41
  <!DOCTYPE html>
38
42
  <html lang="en">
39
43
  <head>
40
44
  <meta charset="UTF-8">
41
45
  <title>Report</title>
42
- <style> body { font-family: Arial, sans-serif; } img { max-width: 100%; } </style>
46
+ <style>
47
+ body { font-family: Arial, sans-serif; }
48
+ .chart-container { margin: 20px 0; }
49
+ </style>
50
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
43
51
  </head>
44
52
  <body>
45
53
  <%- htmlContent %>
@@ -0,0 +1,35 @@
1
+ {
2
+ "dollar_rate_chart": {
3
+ "type": "line",
4
+ "config": {
5
+ "labels": [
6
+ "16.08.2025",
7
+ "19.08.2025",
8
+ "20.08.2025",
9
+ "21.08.2025",
10
+ "22.08.2025",
11
+ "23.08.2025"
12
+ ],
13
+ "datasets": [
14
+ {
15
+ "label": "Курс USD/RUB",
16
+ "data": [
17
+ 80.0224,
18
+ 80.4256,
19
+ 80.3466,
20
+ 80.1045,
21
+ 80.2548,
22
+ 80.7498
23
+ ],
24
+ "borderColor": [
25
+ "rgba(54, 162, 235, 1)"
26
+ ],
27
+ "fill": false
28
+ }
29
+ ],
30
+ "options": {
31
+ "title": "Динамика курса доллара США к рублю"
32
+ }
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,7 @@
1
+ # Отчет о курсе доллара за неделю (16.08.2025 - 23.08.2025)
2
+
3
+ Этот отчет показывает динамику курса доллара США к российскому рублю за последнюю неделю.
4
+
5
+ [[chart:dollar_rate_chart]]
6
+
7
+ Данные взяты с официального источника Банка России.
@@ -0,0 +1,39 @@
1
+ {
2
+ "document": "# Отчет о курсе доллара за неделю (16.08.2025 - 23.08.2025)\n\nЭтот отчет показывает динамику курса доллара США к российскому рублю за последнюю неделю.\n\n[[chart:dollar_rate_chart]]\n\nДанные взяты с официального источника Банка России.",
3
+ "charts": {
4
+ "dollar_rate_chart": {
5
+ "type": "line",
6
+ "config": {
7
+ "labels": [
8
+ "16.08.2025",
9
+ "19.08.2025",
10
+ "20.08.2025",
11
+ "21.08.2025",
12
+ "22.08.2025",
13
+ "23.08.2025"
14
+ ],
15
+ "datasets": [
16
+ {
17
+ "label": "Курс USD/RUB",
18
+ "data": [
19
+ 80.0224,
20
+ 80.4256,
21
+ 80.3466,
22
+ 80.1045,
23
+ 80.2548,
24
+ 80.7498
25
+ ],
26
+ "borderColor": [
27
+ "rgba(54, 162, 235, 1)"
28
+ ],
29
+ "fill": false
30
+ }
31
+ ],
32
+ "options": {
33
+ "title": "Динамика курса доллара США к рублю"
34
+ }
35
+ }
36
+ }
37
+ },
38
+ "outputFile": "dollar_rate_report.html"
39
+ }
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+
3
+ # Set the reports directory
4
+ export REPORTS_DIR="./test_reports"
5
+
6
+ # Create the reports directory if it doesn't exist
7
+ mkdir -p "$REPORTS_DIR"
8
+
9
+ # Generate the report using the CLI
10
+ node dist/index.js generate \
11
+ --document dollar_rate_report.md \
12
+ --charts "$(cat dollar_rate_charts.json)" \
13
+ --output dollar_rate_report.html
14
+
15
+ echo "Report generated in $REPORTS_DIR/dollar_rate_report.html"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vint.tri/report_gen_mcp",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "CLI tool for generating HTML reports with embedded charts",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -13,21 +13,20 @@
13
13
  "test": "echo \"No tests yet\""
14
14
  },
15
15
  "dependencies": {
16
- "@modelcontextprotocol/sdk": "^1.0.0",
17
- "chart.js": "^3.9.1",
18
- "chartjs-node-canvas": "^4.1.6",
16
+ "@modelcontextprotocol/sdk": "^1.0.3",
17
+ "chart.js": "^4.4.4",
19
18
  "commander": "^12.1.0",
20
19
  "ejs": "^3.1.10",
21
20
  "express": "^4.19.2",
22
21
  "fs-extra": "^11.2.0",
23
- "marked": "^13.0.2",
22
+ "marked": "^14.1.2",
24
23
  "zod": "^3.23.8"
25
24
  },
26
25
  "devDependencies": {
27
26
  "@types/ejs": "^3.1.5",
28
27
  "@types/express": "^4.17.21",
29
28
  "@types/fs-extra": "^11.0.4",
30
- "@types/node": "^22.4.1",
29
+ "@types/node": "^22.5.0",
31
30
  "typescript": "^5.5.4"
32
31
  },
33
32
  "license": "MIT",
@@ -0,0 +1,56 @@
1
+ import { spawn } from 'child_process';
2
+ import fs from 'fs';
3
+
4
+ // Read the fixed configuration
5
+ const config = JSON.parse(fs.readFileSync('./dollar_rate_report_fixed.json', 'utf8'));
6
+
7
+ // Set the reports directory
8
+ process.env.REPORTS_DIR = './test_reports';
9
+
10
+ // Spawn the report generator in stdio mode
11
+ const child = spawn('node', ['dist/index.js']);
12
+
13
+ // Listen for data from the child process
14
+ child.stdout.on('data', (data) => {
15
+ console.log(`stdout: ${data}`);
16
+ });
17
+
18
+ child.stderr.on('data', (data) => {
19
+ console.error(`stderr: ${data}`);
20
+ });
21
+
22
+ child.on('close', (code) => {
23
+ console.log(`child process exited with code ${code}`);
24
+ });
25
+
26
+ // Send the MCP initialize message
27
+ const initializeMessage = {
28
+ jsonrpc: "2.0",
29
+ id: 1,
30
+ method: "initialize",
31
+ params: {
32
+ protocolVersion: "2024-08-07",
33
+ capabilities: {},
34
+ clientInfo: {
35
+ name: "test-client",
36
+ version: "1.0.0"
37
+ }
38
+ }
39
+ };
40
+
41
+ child.stdin.write(JSON.stringify(initializeMessage) + '\n');
42
+
43
+ // Send the tool call message
44
+ const toolCallMessage = {
45
+ jsonrpc: "2.0",
46
+ id: 2,
47
+ method: "tools/call",
48
+ params: {
49
+ name: "generate-report",
50
+ arguments: config
51
+ }
52
+ };
53
+
54
+ setTimeout(() => {
55
+ child.stdin.write(JSON.stringify(toolCallMessage) + '\n');
56
+ }, 1000);