@meldocio/mcp-stdio-proxy 1.0.0 → 1.0.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.
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const axios = require('axios');
4
+ const https = require('https');
4
5
  const { URL } = require('url');
5
6
 
6
7
  // JSON-RPC error codes
@@ -40,8 +41,10 @@ process.stdin.on('data', (chunk) => {
40
41
 
41
42
  // Process complete lines
42
43
  for (const line of lines) {
43
- if (line.trim()) {
44
- handleLine(line.trim());
44
+ const trimmed = line.trim();
45
+ // Skip empty lines but don't exit
46
+ if (trimmed) {
47
+ handleLine(trimmed);
45
48
  }
46
49
  }
47
50
  });
@@ -52,18 +55,33 @@ process.stdin.on('end', () => {
52
55
  if (buffer.trim()) {
53
56
  handleLine(buffer.trim());
54
57
  }
58
+ // Don't exit - Claude Desktop may reconnect
59
+ // The process will be terminated by Claude Desktop when needed
55
60
  });
56
61
 
57
- // Handle errors
62
+ // Handle errors - don't exit, just log
58
63
  process.stdin.on('error', (error) => {
59
- sendError(null, JSON_RPC_ERROR_CODES.INTERNAL_ERROR, `Input error: ${error.message}`);
60
- process.exit(1);
64
+ // Log to stderr but don't exit - Claude Desktop may close stdin
65
+ // Silently handle stdin errors - they're normal when Claude Desktop closes the connection
66
+ });
67
+
68
+ // Handle stdout errors
69
+ process.stdout.on('error', (error) => {
70
+ // If stdout is closed (e.g., Claude Desktop disconnected), exit gracefully
71
+ if (error.code === 'EPIPE') {
72
+ process.exit(0);
73
+ }
61
74
  });
62
75
 
63
76
  /**
64
77
  * Handle a single line from stdin
65
78
  */
66
79
  function handleLine(line) {
80
+ // Skip empty lines
81
+ if (!line || !line.trim()) {
82
+ return;
83
+ }
84
+
67
85
  try {
68
86
  const request = JSON.parse(line);
69
87
  handleRequest(request);
@@ -81,11 +99,14 @@ function validateRequest(request) {
81
99
  return { valid: false, error: 'Request must be an object' };
82
100
  }
83
101
 
84
- if (request.jsonrpc !== '2.0') {
102
+ // Allow requests without jsonrpc for compatibility (some MCP clients may omit it)
103
+ if (request.jsonrpc && request.jsonrpc !== '2.0') {
85
104
  return { valid: false, error: 'jsonrpc must be "2.0"' };
86
105
  }
87
106
 
88
- if (!request.method && !Array.isArray(request)) {
107
+ // Allow requests without method if they're notifications (id is null/undefined)
108
+ // But batch requests must be arrays
109
+ if (!request.method && !Array.isArray(request) && request.id !== null && request.id !== undefined) {
89
110
  return { valid: false, error: 'Request must have a method or be a batch array' };
90
111
  }
91
112
 
@@ -96,12 +117,29 @@ function validateRequest(request) {
96
117
  * Handle a JSON-RPC request
97
118
  */
98
119
  async function handleRequest(request) {
120
+ // Handle null/undefined requests
121
+ if (!request) {
122
+ return;
123
+ }
124
+
99
125
  // Handle batch requests (array of requests)
100
126
  if (Array.isArray(request)) {
101
127
  // Process batch requests sequentially
102
128
  for (const req of request) {
103
129
  if (req) {
104
- await processSingleRequest(req);
130
+ // Check if this is a protocol method that should be handled locally
131
+ const method = req.method;
132
+ if (method === 'initialize') {
133
+ handleInitialize(req);
134
+ } else if (method === 'initialized') {
135
+ // Notification - no response needed
136
+ continue;
137
+ } else if (method === 'ping') {
138
+ handlePing(req);
139
+ } else {
140
+ // Forward to backend
141
+ await processSingleRequest(req);
142
+ }
105
143
  }
106
144
  }
107
145
  return;
@@ -114,22 +152,89 @@ async function handleRequest(request) {
114
152
  return;
115
153
  }
116
154
 
155
+ // Handle MCP protocol methods locally (not forwarded to backend)
156
+ const method = request.method;
157
+ if (method === 'initialize') {
158
+ handleInitialize(request);
159
+ return;
160
+ } else if (method === 'initialized') {
161
+ // Notification - no response needed per MCP spec
162
+ return;
163
+ } else if (method === 'ping') {
164
+ handlePing(request);
165
+ return;
166
+ }
167
+
168
+ // All other methods (tools/*, resources/*, etc.) are forwarded to backend
117
169
  await processSingleRequest(request);
118
170
  }
119
171
 
172
+ /**
173
+ * Handle MCP initialize method
174
+ * This is called by Claude Desktop to establish the connection
175
+ */
176
+ function handleInitialize(request) {
177
+ const response = {
178
+ jsonrpc: '2.0',
179
+ id: request.id,
180
+ result: {
181
+ protocolVersion: '2025-06-18',
182
+ capabilities: {
183
+ tools: {},
184
+ resources: {}
185
+ },
186
+ serverInfo: {
187
+ name: '@meldocio/mcp-stdio-proxy',
188
+ version: '1.0.1'
189
+ }
190
+ }
191
+ };
192
+
193
+ process.stdout.write(JSON.stringify(response) + '\n');
194
+ if (process.stdout.isTTY) {
195
+ process.stdout.flush();
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Handle MCP ping method (keep-alive)
201
+ */
202
+ function handlePing(request) {
203
+ const response = {
204
+ jsonrpc: '2.0',
205
+ id: request.id,
206
+ result: {}
207
+ };
208
+
209
+ process.stdout.write(JSON.stringify(response) + '\n');
210
+ if (process.stdout.isTTY) {
211
+ process.stdout.flush();
212
+ }
213
+ }
214
+
120
215
  /**
121
216
  * Process a single JSON-RPC request
217
+ * Forwards the request to the backend MCP API
122
218
  */
123
219
  async function processSingleRequest(request) {
124
220
  try {
221
+ // Ensure request has jsonrpc field
222
+ const requestWithJsonRpc = {
223
+ ...request,
224
+ jsonrpc: request.jsonrpc || '2.0'
225
+ };
226
+
125
227
  // Make HTTP request to MCP API
126
- const response = await axios.post(rpcEndpoint, request, {
228
+ const response = await axios.post(rpcEndpoint, requestWithJsonRpc, {
127
229
  headers: {
128
230
  'Authorization': `Bearer ${token}`,
129
- 'Content-Type': 'application/json'
231
+ 'Content-Type': 'application/json',
232
+ 'User-Agent': '@meldocio/mcp-stdio-proxy/1.0.0'
130
233
  },
131
234
  timeout: REQUEST_TIMEOUT,
132
- validateStatus: (status) => status < 500 // Don't throw on 4xx errors
235
+ validateStatus: (status) => status < 500, // Don't throw on 4xx errors
236
+ // Keep connection alive for better performance
237
+ httpsAgent: new https.Agent({ keepAlive: true, keepAliveMsecs: 1000 })
133
238
  });
134
239
 
135
240
  // Handle successful response
@@ -146,7 +251,12 @@ async function processSingleRequest(request) {
146
251
  }
147
252
  }
148
253
 
254
+ // Ensure stdout is flushed immediately
149
255
  process.stdout.write(JSON.stringify(responseData) + '\n');
256
+ // Flush stdout to ensure data is sent immediately
257
+ if (process.stdout.isTTY) {
258
+ process.stdout.flush();
259
+ }
150
260
  } else {
151
261
  // HTTP error status
152
262
  const errorMessage = response.data?.error?.message ||
@@ -202,4 +312,8 @@ function sendError(id, code, message) {
202
312
  };
203
313
 
204
314
  process.stdout.write(JSON.stringify(errorResponse) + '\n');
315
+ // Flush stdout to ensure data is sent immediately
316
+ if (process.stdout.isTTY) {
317
+ process.stdout.flush();
318
+ }
205
319
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meldocio/mcp-stdio-proxy",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "MCP stdio proxy for meldoc - connects Claude Desktop to meldoc MCP API",
5
5
  "main": "bin/meldoc-mcp-proxy.js",
6
6
  "bin": {
@@ -46,4 +46,4 @@
46
46
  "bin/**/*.js"
47
47
  ]
48
48
  }
49
- }
49
+ }