实现 test 的 vscode webview adapter

This commit is contained in:
huangzhelong.byte 2025-03-28 23:21:23 +08:00
parent 1ac997f8df
commit 2e1454281d
8 changed files with 127 additions and 91 deletions

View File

@ -19,14 +19,14 @@ const { postMessage, onMessage, isConnected } = useMessageBridge();
//
onMessage((message) => {
console.log('Received:', message.command, message.payload);
console.log('Received:', message.command, message.data);
});
//
const sendPing = () => {
postMessage({
command: 'ping',
payload: { timestamp: Date.now() }
data: { timestamp: Date.now() }
});
};

View File

@ -3,7 +3,7 @@ import { onUnmounted, ref } from 'vue';
export interface VSCodeMessage {
command: string;
payload?: unknown;
data?: unknown;
callbackId?: string;
}

View File

@ -0,0 +1,7 @@
## init
```bash
uv sync
```

61
test/src/adapter.ts Normal file
View File

@ -0,0 +1,61 @@
import { WebSocket } from 'ws';
// WebSocket 消息格式
export interface WebSocketMessage {
command: string;
data: any;
}
// 服务器返回的消息格式
export interface WebSocketResponse {
result?: any;
timeCost?: number;
error?: string;
}
// 监听器回调类型
export type MessageHandler = (message: any) => void;
export class VSCodeWebViewLike {
private ws: WebSocket;
private messageHandlers: Set<MessageHandler>;
constructor(ws: WebSocket) {
this.ws = ws;
this.messageHandlers = new Set();
// 监听消息并触发回调
this.ws.on('message', (rawData: Buffer | string) => {
try {
const message: any = JSON.parse(rawData.toString());
this.messageHandlers.forEach((handler) => handler(message));
} catch (error) {
console.error('Failed to parse WebSocket message:', error);
}
});
}
/**
* vscode.webview.postMessage
* @param message - command args
*/
postMessage(message: WebSocketMessage): void {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(message));
} else {
console.error('WebSocket is not open, cannot send message');
}
}
/**
* vscode.webview.onDidReceiveMessage
* @param callback -
* @returns {{ dispose: () => void }} -
*/
onDidReceiveMessage(callback: MessageHandler): { dispose: () => void } {
this.messageHandlers.add(callback);
return {
dispose: () => this.messageHandlers.delete(callback),
};
}
}

View File

@ -8,7 +8,7 @@ type ConnectionType = 'STDIO' | 'SSE';
type McpTransport = StdioClientTransport | SSEClientTransport;
// 定义命令行参数接口
interface MCPOptions {
export interface MCPOptions {
connectionType: ConnectionType;
// STDIO 特定选项
command?: string;
@ -31,8 +31,8 @@ class MCPClient {
this.client = new Client(
{
name: "example-client",
version: "1.0.0"
name: "openmcp test local client",
version: "0.0.1"
},
{
capabilities: {
@ -214,4 +214,4 @@ if (require.main === module) {
process.exit(1);
}
})();
}
}

View File

@ -0,0 +1,31 @@
import { VSCodeWebViewLike } from '../adapter';
import { connect, type MCPOptions } from './connect';
async function connectHandler(option: MCPOptions, webview: VSCodeWebViewLike) {
try {
const client = await connect(option);
const connectResult = {
code: 200,
msg: 'connect success'
};
webview.postMessage({ command: 'connect', data: connectResult });
} catch (error) {
const connectResult = {
code: 500,
msg: error
};
webview.postMessage({ command: 'connect', data: connectResult });
}
}
export function messageController(command: string, data: any, webview: VSCodeWebViewLike) {
switch (command) {
case 'connect':
connectHandler(data, webview);
break;
default:
break;
}
}

View File

@ -1,37 +1,35 @@
// server/wsServer.ts
import WebSocket from 'ws';
import { messageController } from './controller';
import { VSCodeWebViewLike } from './adapter';
export interface VSCodeMessage {
command: string;
payload?: unknown;
data?: unknown;
callbackId?: string;
}
export type MessageHandler = (message: VSCodeMessage) => void;
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
// 转换普通消息为 VS Code 格式
ws.on('message', (data) => {
console.log('receive data from frontend: ' + data.toString());
// const rawMessage = data.toString();
// const vscodeMessage: VSCodeMessage = {
// command: 'ws-message',
// payload: rawMessage,
// callbackId: Math.random().toString(36).slice(2)
// };
// ws.send(JSON.stringify(vscodeMessage));
wss.on('connection', ws => {
// 仿造 webview 进行统一接口访问
const webview = new VSCodeWebViewLike(ws);
// 先发送成功建立的消息
webview.postMessage({
command: 'hello',
data: 'hello'
});
// 连接后发送一个消息
const vscodeMessage: VSCodeMessage = {
command: 'ws-message',
payload: {
text: 'connection completed'
},
callbackId: Math.random().toString(36).slice(2)
};
ws.send(JSON.stringify(vscodeMessage));
// 注册消息接受的管线
webview.onDidReceiveMessage(message => {
try {
const { command, data } = message;
messageController(command, data, webview);
} catch (error) {
console.log('backend, meet error during [message], ', error);
}
});
});

View File

@ -1,61 +0,0 @@
// server/src/wsHandler.ts
import { WebSocketServer, WebSocket } from 'ws';
import { IMessage } from './types';
export class WSServer {
private wss: WebSocketServer;
private clients = new Set<WebSocket>();
constructor(port: number) {
this.wss = new WebSocketServer({ port });
this.setupConnection();
}
private setupConnection() {
this.wss.on('connection', (ws) => {
this.clients.add(ws);
console.log('Client connected');
ws.on('message', (data) => {
try {
const message: IMessage = JSON.parse(data.toString());
this.handleMessage(ws, message);
} catch (err) {
console.error('Message parse error:', err);
}
});
ws.on('close', () => {
this.clients.delete(ws);
console.log('Client disconnected');
});
});
}
private handleMessage(ws: WebSocket, message: IMessage) {
console.log('Received:', message);
// 模拟 VS Code 的 postMessage 响应
if (message.type === 'client-message') {
this.send(ws, {
type: 'server-response',
data: {
original: message.data,
response: 'Message received at ' + new Date().toISOString()
}
});
}
}
public send(ws: WebSocket, message: IMessage) {
if (ws.readyState === ws.OPEN) {
ws.send(JSON.stringify(message));
}
}
public broadcast(message: IMessage) {
this.clients.forEach(client => {
this.send(client, message);
});
}
}