@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 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
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "references": [
9
+ { "path": "../core-ui-engine" }
10
+ ]
11
+ }