This is an automated email from the ASF dual-hosted git repository.

plainheart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/echarts-mcp.git


The following commit(s) were added to refs/heads/main by this push:
     new 1b504d3  feat: upgrade mcp sdk to latest and support configuring local 
storage directory
1b504d3 is described below

commit 1b504d391ed70fb645a6da63480bf601448d8bb5
Author: plainheart <[email protected]>
AuthorDate: Mon Sep 14 14:50:05 2026 +0800

    feat: upgrade mcp sdk to latest and support configuring local storage 
directory
---
 .env.exmaple   |   3 +-
 .gitignore     |   1 +
 package.json   |   6 ++-
 src/index.js   | 128 ++++++++++++++++++++++-----------------------------------
 src/storage.js |  15 +++++--
 5 files changed, 69 insertions(+), 84 deletions(-)

diff --git a/.env.exmaple b/.env.exmaple
index ef61399..6426c8e 100644
--- a/.env.exmaple
+++ b/.env.exmaple
@@ -1,7 +1,8 @@
 SERVER_PORT=8081
 
-# Storage mode: 'local' (saves to ./images/) or 'cloud' (uploads to BaiduBCE 
BOS)
+# Storage mode: 'local' (saves to ./tmp/) or 'cloud' (uploads to BaiduBCE BOS)
 STORAGE_MODE=local
+STORAGE_LOCAL_DIR=tmp
 
 # Cloud Storage Configuration (only needed when STORAGE_MODE=cloud)
 BOS_AK=...
diff --git a/.gitignore b/.gitignore
index df08eff..29b41d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 .env
 node_modules
 tmp
+*.local
\ No newline at end of file
diff --git a/package.json b/package.json
index 9b5f5c3..4542cf2 100644
--- a/package.json
+++ b/package.json
@@ -5,11 +5,13 @@
     "main": "src/index.js",
     "scripts": {
         "dev": "nodemon src/index.js",
+        "start": "node src/index.js",
+        "inspect": "mcp-inspector --server-url http://localhost:8081/mcp 
--transport http",
         "start:stdio": "node src/index.js --stdio",
-        "inspect": "mcp-inspector node ./src/index.js"
+        "inspect:stdio": "mcp-inspector --web node src/index.js --stdio"
     },
     "keywords": ["echarts", "mcp", "data-visualization"],
-    "author": "",
+    "author": "Apache ECharts Team",
     "description": "Apache ECharts MCP Server",
     "dependencies": {
         "@baiducloud/sdk": "^1.0.7",
diff --git a/src/index.js b/src/index.js
index 90f2527..09da0c0 100644
--- a/src/index.js
+++ b/src/index.js
@@ -18,12 +18,12 @@
 */
 
 import express from 'express';
-import { Server } from '@modelcontextprotocol/sdk/server/index.js';
-import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
+import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import { StreamableHTTPServerTransport } from 
'@modelcontextprotocol/sdk/server/streamableHttp.js';
 import { StdioServerTransport } from 
'@modelcontextprotocol/sdk/server/stdio.js';
-import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } 
from '@modelcontextprotocol/sdk/types.js';
+import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
+import { z } from 'zod';
 import dotenv from 'dotenv';
-
 import { getChartBase64 } from './chart.js';
 import { isTreelike, seriesTypes } from './util.js';
 import { saveImage } from './storage.js';
@@ -32,7 +32,7 @@ dotenv.config();
 
 class EChartsServer {
     constructor() {
-        this.server = new Server(
+        this.mcpServer = new McpServer(
             {
                 name: 'echarts',
                 version: '1.0.0',
@@ -46,7 +46,7 @@ class EChartsServer {
 
         this.setupToolHandlers();
 
-        this.server.onerror = (error) => console.error('[MCP Error]', error);
+        this.mcpServer.server.onerror = (error) => console.error('[MCP 
Error]', error);
     }
 
     validateChartType(type) {
@@ -86,53 +86,40 @@ class EChartsServer {
     }
 
     setupToolHandlers() {
-        this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
-            tools: [
-                {
-                    name: 'get-chart',
-                    description: 'Generate an ECharts chart',
-                    inputSchema: {
-                        type: 'object',
-                        properties: {
-                            title: {
-                                type: 'string',
-                                description: 'Chart title',
-                            },
-                            type: {
-                                type: 'string',
-                                description: `Chart type 
(${seriesTypes.join(', ')})`,
-                            },
-                            seriesName: {
-                                type: 'string',
-                                description: 'Series name that will be 
displayed in the legend',
-                            },
-                            data: {
-                                type: 'array',
-                                description:
-                                    'Chart data array. For example: [["A", 
100], ["B", 200], ["C", 300]] for bar/line/pie/scatter charts, or [{ "name": 
"A", "value": 100, "children": [{ "name": "A1", "value": 40}, { "name": "A2", 
"value": 60}]}] for tree charts',
-                            },
-                            xAxisName: {
-                                type: 'string',
-                                description:
-                                    'Name of the first dimension (data[0]) 
including unit, for bar/line/scatter/pie charts. For example, when data is 
[["Apple", 100], ["Banana", 200], ["Cherry", 300]], xAxisName should be 
"Fruit".',
-                            },
-                            yAxisName: {
-                                type: 'string',
-                                description:
-                                    'Name of the second dimension (data[1]) 
including unit, for bar/line/scatter/pie charts. For example, when data is 
[["Apple", 100], ["Banana", 200], ["Cherry", 300]], yAxisName should be "Sales 
(USD)".',
-                            },
-                        },
-                        required: ['type', 'data', 'title', 'seriesName'],
-                    },
-                },
-            ],
-        }));
-
-        this.server.setRequestHandler(CallToolRequestSchema, async (request) 
=> {
-            if (request.params.name === 'get-chart') {
+        this.mcpServer.registerTool(
+            'get-chart',
+            {
+                description: 'Generate an ECharts chart',
+                inputSchema: z.object({
+                    title: z.string()
+                        .describe('Chart title'),
+                    type: z.string()
+                        .describe(`Chart type (${seriesTypes.join(', ')})`),
+                    seriesName: z.string()
+                        .describe('Series name that will be displayed in the 
legend'),
+                    data: z.array(
+                            z.union([
+                                z.record(z.any()),
+                                z.array(z.any())
+                            ])
+                        )
+                        .describe(
+                            'Chart data array. For example: [["A", 100], ["B", 
200], ["C", 300]] for bar/line/pie/scatter charts, or [{ "name": "A", "value": 
100, "children": [{ "name": "A1", "value": 40}, { "name": "A2", "value": 60}]}] 
for tree charts'
+                        ),
+                    xAxisName: z.string()
+                        .optional()
+                        .describe(
+                            'Name of the first dimension (data) including 
unit, for bar/line/scatter/pie charts. For example, when data is [["Apple", 
100], ["Banana", 200], ["Cherry", 300]], xAxisName should be "Fruit".'
+                        ),
+                    yAxisName: z.string()
+                        .optional()
+                        .describe(
+                            'Name of the second dimension (data) including 
unit, for bar/line/scatter/pie charts. For example, when data is [["Apple", 
100], ["Banana", 200], ["Cherry", 300]], yAxisName should be "Sales (USD)".'
+                        ),
+                }),
+            },
+            async ({ type, data, title, seriesName, xAxisName, yAxisName }) => 
{
                 try {
-                    const { type, data, title, seriesName, xAxisName, 
yAxisName } = request.params.arguments;
-
                     this.validateChartType(type);
                     this.validateChartData(data, type);
 
@@ -163,8 +150,7 @@ class EChartsServer {
                     );
                 }
             }
-            throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: 
${request.params.name}`);
-        });
+        );
     }
 
     async run() {
@@ -172,40 +158,26 @@ class EChartsServer {
 
         if (useStdio) {
             const transport = new StdioServerTransport();
-            await this.server.connect(transport);
-            console.error('ECharts MCP Server running on stdio');
+            await this.mcpServer.connect(transport);
+            console.log('ECharts MCP Server running on stdio');
         } else {
             const app = express();
-            const transports = {};
+            app.use(express.json());
 
             app.get('/', (_, res) => {
-                res.send('Apache ECharts MCP Server is running');
-            });
-
-            app.get('/sse', async (_, res) => {
-                const transport = new SSEServerTransport('/messages', res);
-                transports[transport.sessionId] = transport;
-                res.on('close', () => {
-                    delete transports[transport.sessionId];
-                });
-                await this.server.connect(transport);
+                res.send('Apache ECharts MCP Server (Streamable HTTP) is 
running');
             });
 
-            app.post('/messages', async (req, res) => {
-                const sessionId = req.query.sessionId;
-                const transport = transports[sessionId];
-                if (!transport) {
-                    if (!res.headersSent) {
-                        res.status(400).send('No transport found for 
sessionId. Please establish SSE connection first.');
-                    }
-                    return;
-                }
-                await transport.handlePostMessage(req, res);
+            app.post('/mcp', async (req, res) => {
+                const transport = new StreamableHTTPServerTransport();
+                res.on('close', () => transport.close());
+                await this.mcpServer.connect(transport);
+                await transport.handleRequest(req, res, req.body);
             });
 
             const port = process.env.SERVER_PORT || 8081;
             app.listen(port, () => {
-                console.log(`Server is running on port ${port}`);
+                console.log(`Apache ECharts MCP Server (Streamable HTTP) is 
running on port:`, port);
             });
         }
     }
diff --git a/src/storage.js b/src/storage.js
index 291fbc9..df0b7dd 100644
--- a/src/storage.js
+++ b/src/storage.js
@@ -25,10 +25,15 @@ const __filename = fileURLToPath(import.meta.url);
 const __dirname = path.dirname(__filename);
 
 /**
- * Storage mode: 'local' or 'cloud'
- * Set via STORAGE_MODE env variable. Defaults to 'local'.
+ * Storage mode: `'local'` or `'cloud'`
+ * Set via `STORAGE_MODE` env variable. Defaults to `'local'`.
  */
 const storageMode = process.env.STORAGE_MODE || 'local';
+/**
+ * Local storage directory
+ * Set via `STORAGE_LOCAL_DIR` env variable. Defaults to `'tmp'`.
+ */
+const storageLocalDir = process.env.STORAGE_LOCAL_DIR || 'tmp';
 
 export async function saveImage(base64) {
     if (storageMode === 'cloud') {
@@ -39,7 +44,11 @@ export async function saveImage(base64) {
 
 // ─── Local Storage 
───────────────────────────────────────────────────────────
 
-const imagesDir = path.join(__dirname, '../images');
+const imagesDir = path.isAbsolute(storageLocalDir)
+    ? storageLocalDir
+    : path.join(__dirname, '../', storageLocalDir);
+
+storageMode === 'local' && console.log('Images will be stored to:', imagesDir)
 
 function ensureImagesDir() {
     if (!fs.existsSync(imagesDir)) {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to