|
1 | 1 | #!/usr/bin/env node |
2 | 2 |
|
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'; |
20 | 7 |
|
21 | | -import * as Charts from './charts/index'; |
22 | | -import { generateChartByType } from './utils/generateChart'; |
| 8 | +type TransportType = 'stdio' | 'sse' | 'streamable'; |
23 | 9 |
|
24 | 10 | /** |
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) |
27 | 12 | */ |
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 | + } |
39 | 50 | } |
40 | | -); |
| 51 | + |
| 52 | + return options; |
| 53 | +} |
41 | 54 |
|
42 | 55 | /** |
43 | | - * Handler that lists available tools. |
44 | | - * Exposes a single "create_note" tool that lets clients create new notes. |
| 56 | + * Show help information |
45 | 57 | */ |
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 | +} |
51 | 78 |
|
52 | 79 | /** |
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 |
55 | 81 | */ |
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(); |
61 | 84 |
|
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; |
67 | 88 | } |
68 | 89 |
|
69 | 90 | 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 |
83 | 103 | ); |
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); |
120 | 108 | } |
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); |
139 | 112 | } |
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); |
149 | 113 | } |
150 | 114 |
|
151 | 115 | main().catch(error => { |
152 | | - console.error('Server error:', error); |
| 116 | + console.error('Failed to start server:', error); |
153 | 117 | process.exit(1); |
154 | 118 | }); |
| 119 | + |
| 120 | +export { |
| 121 | + createMCPServer, |
| 122 | + startStdioServer, |
| 123 | + startSSEServer, |
| 124 | + startStreamableServer, |
| 125 | +}; |
0 commit comments