mcp-proxy 6.4.2 → 6.4.3
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/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -41,7 +41,7 @@ describe("JSONFilterTransform", () => {
|
|
|
41
41
|
expect(outputLines).toHaveLength(4);
|
|
42
42
|
expect(outputLines[0]).toBe('{"type": "request", "id": 1}');
|
|
43
43
|
expect(outputLines[1]).toBe('{"type": "response", "id": 2}');
|
|
44
|
-
expect(outputLines[2]).toBe('
|
|
44
|
+
expect(outputLines[2]).toBe('{"type": "notification"}');
|
|
45
45
|
expect(outputLines[3]).toBe('{"type": "request", "id": 3}');
|
|
46
46
|
|
|
47
47
|
// Should have warned about non-JSON lines
|
|
@@ -50,7 +50,6 @@ describe("JSONFilterTransform", () => {
|
|
|
50
50
|
expect.arrayContaining([
|
|
51
51
|
"This is not JSON",
|
|
52
52
|
"Another non-JSON line",
|
|
53
|
-
"",
|
|
54
53
|
"Error: something went wrong",
|
|
55
54
|
]),
|
|
56
55
|
);
|
|
@@ -58,6 +57,52 @@ describe("JSONFilterTransform", () => {
|
|
|
58
57
|
consoleWarnSpy.mockRestore();
|
|
59
58
|
});
|
|
60
59
|
|
|
60
|
+
it("extracts JSON from lines with non-JSON prefixes", async () => {
|
|
61
|
+
const input = [
|
|
62
|
+
'Loading...{"type": "request", "id": 1}',
|
|
63
|
+
'WARNING: deprecation{"type": "response", "id": 2}',
|
|
64
|
+
'{"type": "clean", "id": 3}',
|
|
65
|
+
"Pure noise with no JSON",
|
|
66
|
+
].join("\n");
|
|
67
|
+
|
|
68
|
+
const consoleWarnSpy = vi
|
|
69
|
+
.spyOn(console, "warn")
|
|
70
|
+
.mockImplementation(() => {});
|
|
71
|
+
|
|
72
|
+
const readable = Readable.from([input]);
|
|
73
|
+
const transform = new JSONFilterTransform();
|
|
74
|
+
const chunks: Buffer[] = [];
|
|
75
|
+
|
|
76
|
+
const writable = new Writable({
|
|
77
|
+
write(chunk, _encoding, callback) {
|
|
78
|
+
chunks.push(chunk);
|
|
79
|
+
callback();
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await pipeline(readable, transform, writable);
|
|
84
|
+
|
|
85
|
+
const output = Buffer.concat(chunks).toString();
|
|
86
|
+
const outputLines = output.trim().split("\n");
|
|
87
|
+
|
|
88
|
+
expect(outputLines).toHaveLength(3);
|
|
89
|
+
expect(outputLines[0]).toBe('{"type": "request", "id": 1}');
|
|
90
|
+
expect(outputLines[1]).toBe('{"type": "response", "id": 2}');
|
|
91
|
+
expect(outputLines[2]).toBe('{"type": "clean", "id": 3}');
|
|
92
|
+
|
|
93
|
+
// Should have warned about stripped prefixes
|
|
94
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
95
|
+
"[mcp-proxy] stripped non-JSON prefix from output:",
|
|
96
|
+
"Loading...",
|
|
97
|
+
);
|
|
98
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
99
|
+
"[mcp-proxy] stripped non-JSON prefix from output:",
|
|
100
|
+
"WARNING: deprecation",
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
consoleWarnSpy.mockRestore();
|
|
104
|
+
});
|
|
105
|
+
|
|
61
106
|
it("handles incomplete JSON lines across multiple chunks", async () => {
|
|
62
107
|
// Simulate data arriving in chunks where JSON lines are split
|
|
63
108
|
const chunks = [
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { Transform } from "node:stream";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Extracts JSON-RPC messages from a stream that may contain non-JSON output.
|
|
5
|
+
*
|
|
6
|
+
* Lines that start with '{' are passed through as-is. Lines that contain '{'
|
|
7
|
+
* but have a non-JSON prefix (e.g. Python warnings prepended to a JSON message)
|
|
8
|
+
* have the prefix stripped and the JSON portion extracted. Lines with no '{'
|
|
9
|
+
* are dropped entirely.
|
|
6
10
|
*/
|
|
7
11
|
export class JSONFilterTransform extends Transform {
|
|
8
12
|
private buffer = "";
|
|
@@ -13,8 +17,9 @@ export class JSONFilterTransform extends Transform {
|
|
|
13
17
|
|
|
14
18
|
_flush(callback: (error: Error | null, chunk: Buffer | null) => void) {
|
|
15
19
|
// Handle any remaining data in buffer
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
const json = extractJson(this.buffer);
|
|
21
|
+
if (json !== null) {
|
|
22
|
+
callback(null, Buffer.from(json));
|
|
18
23
|
} else {
|
|
19
24
|
callback(null, null);
|
|
20
25
|
}
|
|
@@ -31,14 +36,14 @@ export class JSONFilterTransform extends Transform {
|
|
|
31
36
|
// Keep the last incomplete line in the buffer
|
|
32
37
|
this.buffer = lines.pop() || "";
|
|
33
38
|
|
|
34
|
-
// Filter lines that start with '{'
|
|
35
39
|
const jsonLines = [];
|
|
36
40
|
const nonJsonLines = [];
|
|
37
41
|
|
|
38
42
|
for (const line of lines) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
const json = extractJson(line);
|
|
44
|
+
if (json !== null) {
|
|
45
|
+
jsonLines.push(json);
|
|
46
|
+
} else if (line.trim().length > 0) {
|
|
42
47
|
nonJsonLines.push(line);
|
|
43
48
|
}
|
|
44
49
|
}
|
|
@@ -57,3 +62,31 @@ export class JSONFilterTransform extends Transform {
|
|
|
57
62
|
}
|
|
58
63
|
}
|
|
59
64
|
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Extracts the JSON portion from a line that may have a non-JSON prefix.
|
|
68
|
+
* Returns null if the line contains no '{'.
|
|
69
|
+
*/
|
|
70
|
+
function extractJson(line: string): null | string {
|
|
71
|
+
const trimmed = line.trim();
|
|
72
|
+
if (trimmed.length === 0) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const braceIndex = trimmed.indexOf("{");
|
|
77
|
+
if (braceIndex === -1) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (braceIndex === 0) {
|
|
82
|
+
return trimmed;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// There's a non-JSON prefix — strip it and return from the first '{'
|
|
86
|
+
const jsonPart = trimmed.slice(braceIndex);
|
|
87
|
+
console.warn(
|
|
88
|
+
"[mcp-proxy] stripped non-JSON prefix from output:",
|
|
89
|
+
trimmed.slice(0, braceIndex),
|
|
90
|
+
);
|
|
91
|
+
return jsonPart;
|
|
92
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Simulates a Python-like MCP server that emits non-JSON output to stdout
|
|
4
|
+
* before and during startup. This mimics the behavior of Python servers
|
|
5
|
+
* where warnings, import messages, or unbuffered print() calls can
|
|
6
|
+
* pollute the stdout stream that mcp-proxy reads as JSON-RPC.
|
|
7
|
+
*
|
|
8
|
+
* Related: https://github.com/punkpeye/mcp-proxy/issues/55
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
12
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
|
+
import {
|
|
14
|
+
CallToolRequestSchema,
|
|
15
|
+
ListToolsRequestSchema,
|
|
16
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
17
|
+
|
|
18
|
+
// Simulate non-JSON output on stdout before the server starts,
|
|
19
|
+
// like Python import warnings or startup messages
|
|
20
|
+
const noiseBefore = process.env.NOISE_BEFORE;
|
|
21
|
+
if (noiseBefore) {
|
|
22
|
+
process.stdout.write(noiseBefore);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const server = new Server(
|
|
26
|
+
{
|
|
27
|
+
name: "noisy-stdout-server",
|
|
28
|
+
version: "1.0.0",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
capabilities: {
|
|
32
|
+
tools: {},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
38
|
+
// Simulate noise interleaved with normal responses
|
|
39
|
+
const noiseDuring = process.env.NOISE_DURING;
|
|
40
|
+
if (noiseDuring) {
|
|
41
|
+
process.stdout.write(noiseDuring);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
tools: [
|
|
46
|
+
{
|
|
47
|
+
description: "A test tool",
|
|
48
|
+
inputSchema: {
|
|
49
|
+
properties: {},
|
|
50
|
+
type: "object",
|
|
51
|
+
},
|
|
52
|
+
name: "ping",
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
server.setRequestHandler(CallToolRequestSchema, async () => {
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
text: "pong",
|
|
63
|
+
type: "text" as const,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const transport = new StdioServerTransport();
|
|
70
|
+
await server.connect(transport);
|