@fnet/cli 0.119.1 → 0.119.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fnet/cli",
3
- "version": "0.119.1",
3
+ "version": "0.119.2",
4
4
  "files": [
5
5
  "dist",
6
6
  "template"
@@ -80,77 +80,80 @@
80
80
  const host = args['cli-host'] || args.cli_host || 'localhost';
81
81
  const mcpEndpoint = '/mcp';
82
82
 
83
- // Security: Validate Origin header to prevent DNS rebinding attacks
84
- app.use((req, res, next) => {
85
- const origin = req.get('origin');
86
- if (origin && host === 'localhost') {
87
- const originUrl = new URL(origin);
88
- if (originUrl.hostname !== 'localhost' && originUrl.hostname !== '127.0.0.1') {
89
- return res.status(403).json({ error: 'Forbidden: Invalid origin' });
90
- }
91
- }
92
- next();
93
- });
94
-
95
- // Middleware to check session ID (except for initialization)
96
- const checkSessionId = (req, res, next) => {
97
- // Skip session check for initialization requests
98
- if (req.method === 'POST' && req.body) {
99
- const body = req.body;
100
- // Check if this is an initialization request
101
- if (body.method === 'initialize' ||
102
- (Array.isArray(body) && body.some(item => item.method === 'initialize'))) {
103
- return next();
104
- }
105
- }
83
+ // Map to store transports by session ID
84
+ const transports = {};
106
85
 
107
- // For non-initialization requests, require session ID
86
+ // Handle POST requests for client-to-server communication
87
+ app.post(mcpEndpoint, async (req, res) => {
88
+ // Check for existing session ID
108
89
  const sessionId = req.get('Mcp-Session-Id');
109
- if (!sessionId) {
90
+ let transport;
91
+
92
+ if (sessionId && transports[sessionId]) {
93
+ // Reuse existing transport
94
+ transport = transports[sessionId];
95
+ } else if (!sessionId && req.body && (req.body.method === 'initialize' ||
96
+ (Array.isArray(req.body) && req.body.some(item => item.method === 'initialize')))) {
97
+ // New initialization request
98
+ transport = new StreamableHTTPServerTransport({
99
+ sessionIdGenerator: () => {
100
+ // Generate cryptographically secure session ID
101
+ return require('crypto').randomBytes(16).toString('hex');
102
+ },
103
+ onsessioninitialized: (sessionId) => {
104
+ // Store the transport by session ID
105
+ transports[sessionId] = transport;
106
+ }
107
+ });
108
+
109
+ // Clean up transport when closed
110
+ transport.onclose = () => {
111
+ if (transport.sessionId) {
112
+ delete transports[transport.sessionId];
113
+ }
114
+ };
115
+
116
+ // Connect to the MCP server
117
+ await server.connect(transport);
118
+ } else {
119
+ // Invalid request
110
120
  return res.status(400).json({
111
121
  jsonrpc: '2.0',
112
122
  error: {
113
123
  code: -32000,
114
- message: 'Bad Request: Mcp-Session-Id header is required'
124
+ message: 'Bad Request: No valid session ID provided'
115
125
  },
116
126
  id: null
117
127
  });
118
128
  }
119
- next();
120
- };
121
-
122
- app.use(mcpEndpoint, checkSessionId);
123
129
 
124
- transport = new StreamableHTTPServerTransport({
125
- sessionIdGenerator: () => {
126
- // Generate cryptographically secure session ID
127
- return require('crypto').randomBytes(16).toString('hex');
128
- },
129
- });
130
-
131
- // Handle POST requests for client-to-server messages
132
- app.post(mcpEndpoint, async (req, res) => {
130
+ // Handle the request
133
131
  await transport.handleRequest(req, res, req.body);
134
132
  });
135
133
 
136
- // Handle GET requests for server-to-client SSE stream
137
- app.get(mcpEndpoint, async (req, res) => {
134
+ // Reusable handler for GET and DELETE requests
135
+ const handleSessionRequest = async (req, res) => {
136
+ const sessionId = req.get('Mcp-Session-Id');
137
+ if (!sessionId || !transports[sessionId]) {
138
+ return res.status(400).send('Invalid or missing session ID');
139
+ }
140
+
141
+ const transport = transports[sessionId];
138
142
  await transport.handleRequest(req, res);
139
- });
143
+ };
144
+
145
+ // Handle GET requests for server-to-client notifications via SSE
146
+ app.get(mcpEndpoint, handleSessionRequest);
140
147
 
141
148
  // Handle DELETE requests for session termination
142
- app.delete(mcpEndpoint, async (req, res) => {
143
- await transport.handleRequest(req, res);
144
- });
149
+ app.delete(mcpEndpoint, handleSessionRequest);
145
150
 
146
- const httpServer = app.listen(port, host, () => {
151
+ app.listen(port, host, () => {
147
152
  console.log(`MCP server started with Streamable HTTP transport`);
148
153
  console.log(`Endpoint: http://${host}:${port}${mcpEndpoint}`);
149
154
  console.log(`Listening on ${host}:${port}`);
150
155
  });
151
156
 
152
- // Connect transport to server
153
- await server.connect(transport);
154
157
  return;
155
158
  } else {
156
159
  console.error(`Unknown MCP transport type: ${transportType}`);