@mcp-html-bridge/proxy 0.1.0
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 +24 -0
- package/src/index.ts +14 -0
- package/src/proxy-server.ts +127 -0
- package/tsconfig.json +11 -0
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mcp-html-bridge/proxy",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP proxy that intercepts & enhances responses with HTML UI",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc -p tsconfig.json",
|
|
9
|
+
"clean": "rm -rf dist"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@mcp-html-bridge/ui-engine": "*",
|
|
13
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/zhongkai/mcp-html-bridge.git",
|
|
22
|
+
"directory": "packages/adapter-proxy"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ── Proxy adapter entry point ──
|
|
3
|
+
import { MCPProxyServer } from './proxy-server.js';
|
|
4
|
+
|
|
5
|
+
const targetCommand = process.argv[2];
|
|
6
|
+
if (!targetCommand) {
|
|
7
|
+
console.error('Usage: mcp-proxy <target-server-command> [args...]');
|
|
8
|
+
console.error('Example: mcp-proxy "npx -y @modelcontextprotocol/server-filesystem /tmp"');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const targetArgs = process.argv.slice(3);
|
|
13
|
+
const proxy = new MCPProxyServer(targetCommand, targetArgs);
|
|
14
|
+
proxy.start();
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// ── MCP Proxy Server: intercepts tool results and enhances with HTML ──
|
|
2
|
+
import { spawn, type ChildProcess } from 'node:child_process';
|
|
3
|
+
import { renderFromData } from '@mcp-html-bridge/ui-engine';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* MCP Proxy that sits between a client and a target MCP server.
|
|
7
|
+
* It forwards all traffic transparently, but enhances tool call results
|
|
8
|
+
* with rendered HTML blocks.
|
|
9
|
+
*/
|
|
10
|
+
export class MCPProxyServer {
|
|
11
|
+
private targetProcess: ChildProcess | null = null;
|
|
12
|
+
private clientBuffer = '';
|
|
13
|
+
private serverBuffer = '';
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
private targetCommand: string,
|
|
17
|
+
private targetArgs: string[] = []
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Start the proxy. Reads from stdin, writes to stdout.
|
|
22
|
+
* Spawns the target MCP server as a subprocess.
|
|
23
|
+
*/
|
|
24
|
+
start(): void {
|
|
25
|
+
// Spawn target MCP server
|
|
26
|
+
const [cmd, ...defaultArgs] = this.targetCommand.split(/\s+/);
|
|
27
|
+
this.targetProcess = spawn(cmd, [...defaultArgs, ...this.targetArgs], {
|
|
28
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Forward stderr from target
|
|
32
|
+
this.targetProcess.stderr?.on('data', (chunk: Buffer) => {
|
|
33
|
+
process.stderr.write(chunk);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Client → Target (forward requests transparently)
|
|
37
|
+
process.stdin.on('data', (chunk: Buffer) => {
|
|
38
|
+
this.targetProcess?.stdin?.write(chunk);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Target → Client (intercept and enhance responses)
|
|
42
|
+
this.targetProcess.stdout?.on('data', (chunk: Buffer) => {
|
|
43
|
+
this.serverBuffer += chunk.toString();
|
|
44
|
+
this.processServerBuffer();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
this.targetProcess.on('exit', (code) => {
|
|
48
|
+
process.stderr.write(`[proxy] Target server exited with code ${code}\n`);
|
|
49
|
+
process.exit(code ?? 1);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
process.stdin.on('end', () => {
|
|
53
|
+
this.targetProcess?.stdin?.end();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
process.stderr.write('[proxy] MCP Proxy started\n');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private processServerBuffer(): void {
|
|
60
|
+
while (true) {
|
|
61
|
+
const headerEnd = this.serverBuffer.indexOf('\r\n\r\n');
|
|
62
|
+
if (headerEnd === -1) break;
|
|
63
|
+
|
|
64
|
+
const header = this.serverBuffer.slice(0, headerEnd);
|
|
65
|
+
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
66
|
+
if (!match) {
|
|
67
|
+
// Forward non-JSON-RPC content as-is
|
|
68
|
+
const chunk = this.serverBuffer.slice(0, headerEnd + 4);
|
|
69
|
+
process.stdout.write(chunk);
|
|
70
|
+
this.serverBuffer = this.serverBuffer.slice(headerEnd + 4);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const contentLength = parseInt(match[1], 10);
|
|
75
|
+
const bodyStart = headerEnd + 4;
|
|
76
|
+
if (this.serverBuffer.length < bodyStart + contentLength) break;
|
|
77
|
+
|
|
78
|
+
const body = this.serverBuffer.slice(bodyStart, bodyStart + contentLength);
|
|
79
|
+
this.serverBuffer = this.serverBuffer.slice(bodyStart + contentLength);
|
|
80
|
+
|
|
81
|
+
const enhanced = this.enhanceResponse(body);
|
|
82
|
+
const newHeader = `Content-Length: ${Buffer.byteLength(enhanced)}\r\n\r\n`;
|
|
83
|
+
process.stdout.write(newHeader + enhanced);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private enhanceResponse(body: string): string {
|
|
88
|
+
try {
|
|
89
|
+
const msg = JSON.parse(body) as {
|
|
90
|
+
id?: number;
|
|
91
|
+
result?: { content?: Array<{ type: string; text?: string }> };
|
|
92
|
+
method?: string;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Only enhance tool call results
|
|
96
|
+
if (msg.id === undefined || !msg.result?.content) return body;
|
|
97
|
+
|
|
98
|
+
// Check if this looks like a tools/call response
|
|
99
|
+
const textContent = msg.result.content.find(
|
|
100
|
+
(c) => c.type === 'text' && c.text
|
|
101
|
+
);
|
|
102
|
+
if (!textContent?.text) return body;
|
|
103
|
+
|
|
104
|
+
// Try to parse the text content as JSON for rendering
|
|
105
|
+
let data: unknown;
|
|
106
|
+
try {
|
|
107
|
+
data = JSON.parse(textContent.text);
|
|
108
|
+
} catch {
|
|
109
|
+
// Not JSON — try rendering as reading block
|
|
110
|
+
data = textContent.text;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Generate HTML
|
|
114
|
+
const html = renderFromData(data, { title: 'MCP Result' });
|
|
115
|
+
|
|
116
|
+
// Append HTML block to the response content
|
|
117
|
+
msg.result.content.push({
|
|
118
|
+
type: 'text',
|
|
119
|
+
text: `\n\`\`\`mcp-html\n${html}\n\`\`\``,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return JSON.stringify(msg);
|
|
123
|
+
} catch {
|
|
124
|
+
return body;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|