Skip to content

Commit f60add6

Browse files
committed
refactor: support sse and streamable
1 parent bb284d6 commit f60add6

11 files changed

Lines changed: 838 additions & 139 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
4545
},
4646
"dependencies": {
47-
"@modelcontextprotocol/sdk": "^1.12.0",
47+
"@modelcontextprotocol/sdk": "^1.15.0",
4848
"@visactor/generate-vchart": "^2.0.8",
4949
"@visactor/vutils": "^1.0.7",
5050
"axios": "^1.10.0",

pnpm-lock.yaml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/charts/scatter.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { z } from "zod";
2-
import { convertZodToJsonSchema } from "../utils/schema";
1+
import { z } from 'zod';
2+
import { convertZodToJsonSchema } from '../utils/schema';
33
import {
44
BackgroundSchema,
55
ChartOutputSchema,
@@ -24,7 +24,7 @@ import {
2424
YAxisTitleSchema,
2525
YAxisTypeSchema,
2626
YFieldSchema,
27-
} from "./common";
27+
} from './common';
2828

2929
const schema = z.object({
3030
output: ChartOutputSchema,
@@ -33,12 +33,11 @@ const schema = z.object({
3333
dataTable: z
3434
.array(z.any())
3535
.describe("Scatter chart data, e.g., [{ x: 34, y: 10, category: 'A' }].")
36-
.nonempty({ message: "Scatter chart data cannot be empty." }),
37-
transpose: z.boolean().optional(),
36+
.nonempty({ message: 'Scatter chart data cannot be empty.' }),
3837

3938
xField: YFieldSchema,
4039
yField: YFieldSchema,
41-
sizeField: z.string().nullish().describe("Numeric field for bubble size."),
40+
sizeField: z.string().nullish().describe('Numeric field for bubble size.'),
4241
colorField: ColorFieldSchema,
4342

4443
chartTheme: ThemeSchema,
@@ -64,9 +63,9 @@ const schema = z.object({
6463
});
6564

6665
const tool = {
67-
name: "generate_scatter_chart",
66+
name: 'generate_scatter_chart',
6867
description:
69-
"Generate a scatter chart to visually display the distribution, clustering trends, and correlations of data points in two-dimensional or multi-dimensional space. Suitable for analyzing relationships between variables, outlier detection, and similar scenarios.",
68+
'Generate a scatter chart to visually display the distribution, clustering trends, and correlations of data points in two-dimensional or multi-dimensional space. Suitable for analyzing relationships between variables, outlier detection, and similar scenarios.',
7069
inputSchema: convertZodToJsonSchema(schema),
7170
};
7271

src/index.ts

Lines changed: 100 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,154 +1,125 @@
11
#!/usr/bin/env node
22

3-
/**
4-
* This is a template MCP server that implements a simple notes system.
5-
* It demonstrates core MCP concepts like resources and tools by allowing:
6-
* - Listing notes as resources
7-
* - Reading individual notes
8-
* - Creating new notes via a tool
9-
* - Summarizing all notes via a prompt
10-
*/
11-
12-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
13-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
14-
import {
15-
CallToolRequestSchema,
16-
ListToolsRequestSchema,
17-
McpError,
18-
ErrorCode,
19-
} from '@modelcontextprotocol/sdk/types.js';
3+
import { createMCPServer } from './server';
4+
import { startSSEServer } from './services/sse';
5+
import { startStdioServer } from './services/stdio';
6+
import { startStreamableServer } from './services/streamable';
207

21-
import * as Charts from './charts/index';
22-
import { generateChartByType } from './utils/generateChart';
8+
type TransportType = 'stdio' | 'sse' | 'streamable';
239

2410
/**
25-
* Create an MCP server with capabilities for resources (to list/read notes),
26-
* tools (to create new notes), and prompts (to summarize notes).
11+
* Simple CLI argument parser (no external dependencies)
2712
*/
28-
const server = new Server(
29-
{
30-
name: 'vchart-mcp-server',
31-
version: '0.1.3',
32-
},
33-
{
34-
capabilities: {
35-
resources: {},
36-
tools: {},
37-
prompts: {},
38-
},
13+
function parseArgs() {
14+
const args = process.argv.slice(2);
15+
const options: {
16+
transport?: TransportType;
17+
help?: boolean;
18+
port?: number;
19+
endpoint?: string;
20+
} = {
21+
transport: 'stdio' as TransportType,
22+
help: false,
23+
};
24+
25+
for (let i = 0; i < args.length; i++) {
26+
const arg = args[i];
27+
28+
if (arg === '--help' || arg === '-h') {
29+
options.help = true;
30+
} else if (arg === '--transport' || arg === '-t') {
31+
const nextArg = args[i + 1];
32+
if (nextArg && ['stdio', 'sse', 'streamable'].includes(nextArg)) {
33+
options.transport = nextArg as TransportType;
34+
i++; // skip next argument
35+
}
36+
} else if (arg === '--port' || arg === '-p') {
37+
const nextArg = args[i + 1];
38+
if (nextArg && !isNaN(parseInt(nextArg))) {
39+
options.port = parseInt(nextArg);
40+
i++; // skip next argument
41+
}
42+
} else if (arg === '--endpoint' || arg === '-e') {
43+
const nextArg = args[i + 1];
44+
if (nextArg) {
45+
// 确保 endpoint 以 '/' 开头
46+
options.endpoint = nextArg.startsWith('/') ? nextArg : `/${nextArg}`;
47+
i++; // skip next argument
48+
}
49+
}
3950
}
40-
);
51+
52+
return options;
53+
}
4154

4255
/**
43-
* Handler that lists available tools.
44-
* Exposes a single "create_note" tool that lets clients create new notes.
56+
* Show help information
4557
*/
46-
server.setRequestHandler(ListToolsRequestSchema, async () => {
47-
return {
48-
tools: Object.values(Charts).map(chart => (chart as any).tool),
49-
};
50-
});
58+
function showHelp() {
59+
console.log(`
60+
VChart MCP Server v0.1.3
61+
Chart generation server with multiple transport modes
62+
63+
Usage: node index.js [options]
64+
65+
Options:
66+
-t, --transport <type> Transport type (stdio, sse, streamable) [default: stdio]
67+
-p, --port <port> Port number for HTTP-based transports [default: 3000]
68+
-e, --endpoint <path> Endpoint path for HTTP-based transports [default: /message]
69+
-h, --help Show this help message
70+
71+
Examples:
72+
node index.js # Start with stdio transport
73+
node index.js -t sse -p 3000 # Start with SSE transport on port 3000
74+
node index.js -t sse -p 3000 -e /api/sse # Start with SSE transport on custom endpoint
75+
node index.js -t streamable -p 3001 -e /stream # Start with streamable transport on custom endpoint
76+
`);
77+
}
5178

5279
/**
53-
* Handler for the create_note tool.
54-
* Creates a new note with the provided title and content, and returns success message.
80+
* Main CLI application
5581
*/
56-
server.setRequestHandler(CallToolRequestSchema, async request => {
57-
const toolName = request.params.name;
58-
const chartType = Object.keys(Charts).find(
59-
key => (Charts as any)[key].tool.name === toolName
60-
);
82+
async function main() {
83+
const options = parseArgs();
6184

62-
if (!chartType) {
63-
throw new McpError(
64-
ErrorCode.MethodNotFound,
65-
`Unknown tool: ${request.params.name}.`
66-
);
85+
if (options.help) {
86+
showHelp();
87+
return;
6788
}
6889

6990
try {
70-
// Validate input using Zod before sending to API.
71-
const args = request.params.arguments || {};
72-
73-
// Select the appropriate schema based on the chart type.
74-
const schema = Charts[chartType as keyof typeof Charts].schema;
75-
76-
if (schema) {
77-
// Use safeParse instead of parse and try-catch.
78-
const result = schema.safeParse(args);
79-
if (!result.success) {
80-
throw new McpError(
81-
ErrorCode.InvalidParams,
82-
`Invalid parameters: ${result.error.message}`
91+
switch (options.transport) {
92+
case 'stdio':
93+
await startStdioServer(createMCPServer());
94+
break;
95+
case 'sse':
96+
await startSSEServer(createMCPServer(), options.endpoint, options.port);
97+
break;
98+
case 'streamable':
99+
await startStreamableServer(
100+
createMCPServer,
101+
options.endpoint,
102+
options.port
83103
);
84-
}
85-
}
86-
87-
const res = await generateChartByType(chartType, args);
88-
89-
if (res && (res as any).spec) {
90-
return {
91-
content: [
92-
{
93-
type: 'text',
94-
text: JSON.stringify((res as any).spec, null, 2),
95-
},
96-
],
97-
};
98-
}
99-
100-
if (res && (res as any).image) {
101-
return {
102-
content: [
103-
{
104-
type: 'text',
105-
text: (res as any).image,
106-
},
107-
],
108-
};
109-
}
110-
111-
if (res && (res as any).html) {
112-
return {
113-
content: [
114-
{
115-
type: 'text',
116-
text: (res as any).html,
117-
},
118-
],
119-
};
104+
break;
105+
default:
106+
console.error(`Unknown transport type: ${options.transport}`);
107+
process.exit(1);
120108
}
121-
122-
return {
123-
content: [
124-
{
125-
type: 'text',
126-
text: 'Failed to generate chart',
127-
},
128-
],
129-
};
130-
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
131-
} catch (error: any) {
132-
if (error instanceof McpError) {
133-
throw error;
134-
}
135-
throw new McpError(
136-
ErrorCode.InternalError,
137-
`Failed to generate chart: ${error?.message || 'Unknown error.'}`
138-
);
109+
} catch (error) {
110+
console.error('Server error:', error);
111+
process.exit(1);
139112
}
140-
});
141-
142-
/**
143-
* Start the server using stdio transport.
144-
* This allows the server to communicate via standard input/output streams.
145-
*/
146-
async function main() {
147-
const transport = new StdioServerTransport();
148-
await server.connect(transport);
149113
}
150114

151115
main().catch(error => {
152-
console.error('Server error:', error);
116+
console.error('Failed to start server:', error);
153117
process.exit(1);
154118
});
119+
120+
export {
121+
createMCPServer,
122+
startStdioServer,
123+
startSSEServer,
124+
startStreamableServer,
125+
};

0 commit comments

Comments
 (0)