@mcp-html-bridge/mcp-client 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 +29 -0
- package/src/client.ts +125 -0
- package/src/index.ts +2 -0
- package/src/types.ts +18 -0
- package/tsconfig.json +11 -0
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mcp-html-bridge/mcp-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP protocol client for schema extraction",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc -p tsconfig.json",
|
|
15
|
+
"clean": "rm -rf dist"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/zhongkai/mcp-html-bridge.git",
|
|
27
|
+
"directory": "packages/core-mcp-client"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// ── MCP Protocol Client: spawn subprocess & extract schemas ──
|
|
2
|
+
import { spawn, type ChildProcess } from 'node:child_process';
|
|
3
|
+
import type { MCPConnectionOptions, MCPServerCapabilities, MCPToolParam } from './types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lightweight MCP client that connects via stdio transport.
|
|
7
|
+
* Implements the minimum protocol handshake to list tools.
|
|
8
|
+
*/
|
|
9
|
+
export class MCPClient {
|
|
10
|
+
private process: ChildProcess | null = null;
|
|
11
|
+
private buffer = '';
|
|
12
|
+
private requestId = 0;
|
|
13
|
+
private pending = new Map<number, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();
|
|
14
|
+
|
|
15
|
+
async connect(options: MCPConnectionOptions): Promise<void> {
|
|
16
|
+
const [cmd, ...defaultArgs] = options.command.split(/\s+/);
|
|
17
|
+
const args = [...defaultArgs, ...(options.args ?? [])];
|
|
18
|
+
|
|
19
|
+
this.process = spawn(cmd, args, {
|
|
20
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
21
|
+
env: { ...process.env, ...options.env },
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
this.process.stdout?.on('data', (chunk: Buffer) => {
|
|
25
|
+
this.buffer += chunk.toString();
|
|
26
|
+
this.processBuffer();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
this.process.stderr?.on('data', (chunk: Buffer) => {
|
|
30
|
+
// Log MCP server stderr for debugging
|
|
31
|
+
process.stderr.write(`[mcp-server] ${chunk.toString()}`);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Initialize handshake
|
|
35
|
+
await this.sendRequest('initialize', {
|
|
36
|
+
protocolVersion: '2024-11-05',
|
|
37
|
+
capabilities: {},
|
|
38
|
+
clientInfo: { name: 'mcp-html-bridge', version: '0.1.0' },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Send initialized notification
|
|
42
|
+
this.sendNotification('notifications/initialized', {});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async listTools(): Promise<MCPToolParam[]> {
|
|
46
|
+
const result = await this.sendRequest('tools/list', {}) as { tools: MCPToolParam[] };
|
|
47
|
+
return result.tools ?? [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async getServerInfo(): Promise<MCPServerCapabilities> {
|
|
51
|
+
const tools = await this.listTools();
|
|
52
|
+
return {
|
|
53
|
+
name: 'mcp-server',
|
|
54
|
+
version: '0.0.0',
|
|
55
|
+
tools,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async disconnect(): Promise<void> {
|
|
60
|
+
if (this.process) {
|
|
61
|
+
this.process.stdin?.end();
|
|
62
|
+
this.process.kill();
|
|
63
|
+
this.process = null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private sendRequest(method: string, params: Record<string, unknown>): Promise<unknown> {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const id = ++this.requestId;
|
|
70
|
+
this.pending.set(id, { resolve, reject });
|
|
71
|
+
|
|
72
|
+
const msg = JSON.stringify({ jsonrpc: '2.0', id, method, params });
|
|
73
|
+
this.process?.stdin?.write(`Content-Length: ${Buffer.byteLength(msg)}\r\n\r\n${msg}`);
|
|
74
|
+
|
|
75
|
+
// Timeout after 30s
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
if (this.pending.has(id)) {
|
|
78
|
+
this.pending.delete(id);
|
|
79
|
+
reject(new Error(`Request ${method} timed out`));
|
|
80
|
+
}
|
|
81
|
+
}, 30000);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private sendNotification(method: string, params: Record<string, unknown>): void {
|
|
86
|
+
const msg = JSON.stringify({ jsonrpc: '2.0', method, params });
|
|
87
|
+
this.process?.stdin?.write(`Content-Length: ${Buffer.byteLength(msg)}\r\n\r\n${msg}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private processBuffer(): void {
|
|
91
|
+
while (true) {
|
|
92
|
+
const headerEnd = this.buffer.indexOf('\r\n\r\n');
|
|
93
|
+
if (headerEnd === -1) break;
|
|
94
|
+
|
|
95
|
+
const header = this.buffer.slice(0, headerEnd);
|
|
96
|
+
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
97
|
+
if (!match) {
|
|
98
|
+
this.buffer = this.buffer.slice(headerEnd + 4);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const contentLength = parseInt(match[1], 10);
|
|
103
|
+
const bodyStart = headerEnd + 4;
|
|
104
|
+
if (this.buffer.length < bodyStart + contentLength) break;
|
|
105
|
+
|
|
106
|
+
const body = this.buffer.slice(bodyStart, bodyStart + contentLength);
|
|
107
|
+
this.buffer = this.buffer.slice(bodyStart + contentLength);
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const msg = JSON.parse(body) as { id?: number; result?: unknown; error?: { message: string } };
|
|
111
|
+
if (msg.id !== undefined && this.pending.has(msg.id)) {
|
|
112
|
+
const handler = this.pending.get(msg.id)!;
|
|
113
|
+
this.pending.delete(msg.id);
|
|
114
|
+
if (msg.error) {
|
|
115
|
+
handler.reject(new Error(msg.error.message));
|
|
116
|
+
} else {
|
|
117
|
+
handler.resolve(msg.result);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
// Ignore malformed messages
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// ── MCP Client types ──
|
|
2
|
+
export interface MCPToolParam {
|
|
3
|
+
name: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
inputSchema: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface MCPConnectionOptions {
|
|
9
|
+
command: string;
|
|
10
|
+
args?: string[];
|
|
11
|
+
env?: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface MCPServerCapabilities {
|
|
15
|
+
name: string;
|
|
16
|
+
version: string;
|
|
17
|
+
tools: MCPToolParam[];
|
|
18
|
+
}
|