优化 web 端开发的隐私保护

This commit is contained in:
锦恢 2025-04-30 03:58:54 +08:00
parent 2b1c0c30dd
commit 0bea084c35
10 changed files with 260 additions and 1547 deletions

View File

@ -25,4 +25,9 @@ $serviceJob = Start-Job -ScriptBlock {
$rendererJob | Wait-Job | Receive-Job $rendererJob | Wait-Job | Receive-Job
$serviceJob | Wait-Job | Receive-Job $serviceJob | Wait-Job | Receive-Job
# 将 resources 目录复制到 software/resources
New-Item -ItemType Directory -Path ./software/resources -Force
Remove-Item -Recurse -Force ./software/resources/* -ErrorAction SilentlyContinue
Copy-Item -Recurse -Path ./resources -Destination ./software/ -Force
Write-Output "finish building services in ./resources" Write-Output "finish building services in ./resources"

1320
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,6 @@
"core-js": "^3.8.3", "core-js": "^3.8.3",
"element-plus": "^2.9.7", "element-plus": "^2.9.7",
"katex": "^0.16.21", "katex": "^0.16.21",
"loadash": "^1.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"markdown-it-katex": "^2.0.3", "markdown-it-katex": "^2.0.3",
@ -8623,13 +8622,6 @@
"uc.micro": "^2.0.0" "uc.micro": "^2.0.0"
} }
}, },
"node_modules/loadash": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/loadash/-/loadash-1.0.0.tgz",
"integrity": "sha512-xlX5HBsXB3KG0FJbJJG/3kYWCfsCyCSus3T+uHVu6QL6YxAdggmm3QeyLgn54N2yi5/UE6xxL5ZWJAAiHzHYEg==",
"deprecated": "Package is unsupport. Please use the lodash package instead.",
"license": "ISC"
},
"node_modules/loader-runner": { "node_modules/loader-runner": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmmirror.com/loader-runner/-/loader-runner-4.3.0.tgz", "resolved": "https://registry.npmmirror.com/loader-runner/-/loader-runner-4.3.0.tgz",

View File

@ -15,7 +15,7 @@ import MainPanel from '@/components/main-panel/index.vue';
import { setDefaultCss } from './hook/css'; import { setDefaultCss } from './hook/css';
import { greenLog, pinkLog } from './views/setting/util'; import { greenLog, pinkLog } from './views/setting/util';
import { acquireVsCodeApi, useMessageBridge } from './api/message-bridge'; import { acquireVsCodeApi, useMessageBridge } from './api/message-bridge';
import { connectionArgs, connectionMethods, doConnect, launchConnect, loadEnvVar } from './views/connect/connection'; import { connectionArgs, connectionMethods, doWebConnect, doVscodeConnect, loadEnvVar } from './views/connect/connection';
import { loadSetting } from './hook/setting'; import { loadSetting } from './hook/setting';
import { loadPanels } from './hook/panel'; import { loadPanels } from './hook/panel';
@ -29,11 +29,6 @@ bridge.addCommandListener('hello', data => {
function initDebug() { function initDebug() {
connectionArgs.commandString = 'node /Users/bytedance/projects/mcp/servers/src/puppeteer/dist/index.js';
// connectionArgs.commandString = 'node C:/Users/K/code/servers/src/puppeteer/dist/index.js';
// connectionArgs.commandString = 'uv run mcp run bing-picture.py';
connectionArgs.cwd = '../servers';
connectionMethods.current = 'STDIO';
setTimeout(async () => { setTimeout(async () => {
// //
@ -43,7 +38,7 @@ function initDebug() {
loadEnvVar(); loadEnvVar();
// //
await doConnect(); await doWebConnect();
// tab // tab
loadPanels(); loadPanels();
@ -66,7 +61,7 @@ async function initProduce() {
loadEnvVar(); loadEnvVar();
// //
await launchConnect(); await doVscodeConnect();
// tab // tab
await loadPanels(); await loadPanels();

View File

@ -1,7 +1,7 @@
import { useMessageBridge } from '@/api/message-bridge'; import { useMessageBridge } from '@/api/message-bridge';
import { reactive } from 'vue'; import { reactive } from 'vue';
import { pinkLog } from '../setting/util'; import { pinkLog } from '../setting/util';
import { ElMessage } from 'element-plus'; import { arrowMiddleware, ElMessage } from 'element-plus';
import { ILaunchSigature } from '@/hook/type'; import { ILaunchSigature } from '@/hook/type';
export const connectionMethods = reactive({ export const connectionMethods = reactive({
@ -69,121 +69,79 @@ export interface McpOptions {
clientVersion?: string; clientVersion?: string;
} }
export function doConnect() { export async function doWebConnect(option: { updateCommandString?: boolean } = {}) {
let connectOption: McpOptions;
const bridge = useMessageBridge();
const env = makeEnv();
return new Promise((resolve, reject) => {
// 监听 connect
bridge.addCommandListener('connect', async data => {
const { code, msg } = data;
connectionResult.success = (code === 200);
if (code === 200) {
const res = await getServerVersion() as { name: string, version: string };
connectionResult.serverInfo.name = res.name || '';
connectionResult.serverInfo.version = res.version || '';
connectionResult.logString.push({
type: 'info',
message: msg
});
} else {
ElMessage({
type: 'error',
message: msg
});
connectionResult.logString.push({
type: 'error',
message: msg
});
}
resolve(void 0);
}, { once: true });
if (connectionMethods.current === 'STDIO') {
if (connectionArgs.commandString.length === 0) {
return;
}
const commandComponents = connectionArgs.commandString.split(/\s+/g);
const command = commandComponents[0];
commandComponents.shift();
connectOption = {
connectionType: 'STDIO',
command: command,
cwd: connectionArgs.cwd,
args: commandComponents,
env: env,
clientName: 'openmcp.connect.stdio',
clientVersion: '0.0.1'
}
} else {
const url = connectionArgs.urlString;
if (url.length === 0) {
return;
}
connectOption = {
connectionType: 'SSE',
url: url,
env: env,
clientName: 'openmcp.connect.sse',
clientVersion: '0.0.1'
}
}
bridge.postMessage({
command: 'connect',
data: connectOption
});
});
}
/**
* @description vscode
*/
export async function launchConnect(option: { updateCommandString?: boolean } = {}) {
// 本地开发只用 IPC 进行启动
// 后续需要考虑到不同的连接方式
const { const {
// updateCommandString 为 true 代表是初始化阶段
updateCommandString = true updateCommandString = true
} = option; } = option;
connectionMethods.current = 'STDIO'; if (updateCommandString) {
pinkLog('请求启动参数');
const connectionItem = await getLaunchSignature('web/launch-signature');
pinkLog('请求启动参数'); if (connectionItem.type ==='stdio') {
const connectionItem = await getLaunchSignature(); connectionMethods.current = 'STDIO';
if (connectionItem.type === 'stdio') {
if (updateCommandString) {
connectionArgs.commandString = connectionItem.commandString; connectionArgs.commandString = connectionItem.commandString;
connectionArgs.cwd = connectionItem.cwd; connectionArgs.cwd = connectionItem.cwd;
if (connectionArgs.commandString.length === 0) { if (connectionArgs.commandString.length === 0) {
return; return;
} }
} } else {
connectionMethods.current = 'SSE';
await launchStdio();
} else {
if (updateCommandString) {
connectionArgs.urlString = connectionItem.url; connectionArgs.urlString = connectionItem.url;
if (connectionArgs.urlString.length === 0) { if (connectionArgs.urlString.length === 0) {
return; return;
} }
} }
}
if (connectionMethods.current === 'STDIO') {
await launchStdio();
} else {
await launchSSE(); await launchSSE();
}
}
/**
* @description vscode
*/
export async function doVscodeConnect(option: { updateCommandString?: boolean } = {}) {
// 本地开发只用 IPC 进行启动
// 后续需要考虑到不同的连接方式
const {
// updateCommandString 为 true 代表是初始化阶段
updateCommandString = true
} = option;
if (updateCommandString) {
pinkLog('请求启动参数');
const connectionItem = await getLaunchSignature('vscode/launch-signature');
if (connectionItem.type ==='stdio') {
connectionMethods.current = 'STDIO';
connectionArgs.commandString = connectionItem.commandString;
connectionArgs.cwd = connectionItem.cwd;
if (connectionArgs.commandString.length === 0) {
return;
}
} else {
connectionMethods.current = 'SSE';
connectionArgs.urlString = connectionItem.url;
if (connectionArgs.urlString.length === 0) {
return;
}
}
}
if (connectionMethods.current === 'STDIO') {
await launchStdio();
} else {
await launchSSE();
} }
} }
@ -191,158 +149,136 @@ async function launchStdio() {
const bridge = useMessageBridge(); const bridge = useMessageBridge();
const env = makeEnv(); const env = makeEnv();
return new Promise<void>((resolve, reject) => { const commandComponents = connectionArgs.commandString.split(/\s+/g);
// 监听 connect const command = commandComponents[0];
bridge.addCommandListener('connect', async data => { commandComponents.shift();
const { code, msg } = data;
connectionResult.success = (code === 200);
if (code === 200) { const connectOption = {
connectionResult.logString.push({ connectionType: 'STDIO',
type: 'info', command: command,
message: msg args: commandComponents,
}); cwd: connectionArgs.cwd,
clientName: 'openmcp.connect.stdio',
clientVersion: '0.0.1',
env
};
const res = await getServerVersion() as { name: string, version: string }; const { code, msg } = await bridge.commandRequest('connect', connectOption);
connectionResult.serverInfo.name = res.name || '';
connectionResult.serverInfo.version = res.version || '';
// 同步信息到 vscode connectionResult.success = (code === 200);
const commandComponents = connectionArgs.commandString.split(/\s+/g);
const command = commandComponents[0];
commandComponents.shift();
const clientStdioConnectionItem = { if (code === 200) {
serverInfo: connectionResult.serverInfo, connectionResult.logString.push({
connectionType: 'STDIO', type: 'info',
name: 'openmcp.connect.stdio', message: msg
command: command, });
args: commandComponents,
cwd: connectionArgs.cwd,
env
};
bridge.postMessage({
command: 'vscode/update-connection-sigature',
data: JSON.parse(JSON.stringify(clientStdioConnectionItem))
});
} else {
connectionResult.logString.push({
type: 'error',
message: msg
});
ElMessage({
type: 'error',
message: msg
});
}
resolve(void 0);
}, { once: true });
const res = await getServerVersion() as { name: string, version: string };
connectionResult.serverInfo.name = res.name || '';
connectionResult.serverInfo.version = res.version || '';
// 同步信息到 vscode
const commandComponents = connectionArgs.commandString.split(/\s+/g); const commandComponents = connectionArgs.commandString.split(/\s+/g);
const command = commandComponents[0]; const command = commandComponents[0];
commandComponents.shift(); commandComponents.shift();
const connectOption = { const clientStdioConnectionItem = {
serverInfo: connectionResult.serverInfo,
connectionType: 'STDIO', connectionType: 'STDIO',
name: 'openmcp.connect.stdio',
command: command, command: command,
args: commandComponents, args: commandComponents,
cwd: connectionArgs.cwd, cwd: connectionArgs.cwd,
clientName: 'openmcp.connect.stdio',
clientVersion: '0.0.1',
env env
}; };
bridge.postMessage({ bridge.postMessage({
command: 'connect', command: 'vscode/update-connection-sigature',
data: connectOption data: JSON.parse(JSON.stringify(clientStdioConnectionItem))
}); });
});
} else {
connectionResult.logString.push({
type: 'error',
message: msg
});
ElMessage({
type: 'error',
message: msg
});
}
} }
async function launchSSE() { async function launchSSE() {
const bridge = useMessageBridge(); const bridge = useMessageBridge();
const env = makeEnv(); const env = makeEnv();
return new Promise<void>((resolve, reject) => { const connectOption: McpOptions = {
// 监听 connect connectionType: 'SSE',
bridge.addCommandListener('connect', async data => { url: connectionArgs.urlString,
const { code, msg } = data; clientName: 'openmcp.connect.sse',
connectionResult.success = (code === 200); clientVersion: '0.0.1',
env
};
if (code === 200) { const { code, msg } = await bridge.commandRequest('connect', connectOption);
connectionResult.logString.push({
type: 'info',
message: msg
});
const res = await getServerVersion() as { name: string, version: string }; connectionResult.success = (code === 200);
connectionResult.serverInfo.name = res.name || '';
connectionResult.serverInfo.version = res.version || '';
// 同步信息到 vscode if (code === 200) {
const clientSseConnectionItem = { connectionResult.logString.push({
serverInfo: connectionResult.serverInfo, type: 'info',
connectionType: 'SSE', message: msg
name: 'openmcp.connect.sse', });
url: connectionArgs.urlString,
oauth: connectionArgs.oauth,
env: env
};
bridge.postMessage({
command: 'vscode/update-connection-sigature',
data: JSON.parse(JSON.stringify(clientSseConnectionItem))
});
} else { const res = await getServerVersion() as { name: string, version: string };
connectionResult.logString.push({ connectionResult.serverInfo.name = res.name || '';
type: 'error', connectionResult.serverInfo.version = res.version || '';
message: msg
});
ElMessage({ // 同步信息到 vscode
type: 'error', const clientSseConnectionItem = {
message: msg serverInfo: connectionResult.serverInfo,
});
}
resolve(void 0);
}, { once: true });
const connectOption: McpOptions = {
connectionType: 'SSE', connectionType: 'SSE',
name: 'openmcp.connect.sse',
url: connectionArgs.urlString, url: connectionArgs.urlString,
clientName: 'openmcp.connect.sse', oauth: connectionArgs.oauth,
clientVersion: '0.0.1', env: env
env
}; };
bridge.postMessage({ bridge.postMessage({
command: 'connect', command: 'vscode/update-connection-sigature',
data: connectOption data: JSON.parse(JSON.stringify(clientSseConnectionItem))
}); });
});
} else {
connectionResult.logString.push({
type: 'error',
message: msg
});
ElMessage({
type: 'error',
message: msg
});
}
} }
function getLaunchSignature() { function getLaunchSignature(signatureName: string) {
return new Promise<ILaunchSigature>((resolve, reject) => { return new Promise<ILaunchSigature>((resolve, reject) => {
// 与 vscode 进行同步 // 与 vscode 进行同步
const bridge = useMessageBridge(); const bridge = useMessageBridge();
bridge.addCommandListener('vscode/launch-signature', data => { bridge.addCommandListener(signatureName, data => {
pinkLog('收到启动参数'); pinkLog('收到启动参数');
resolve(data.msg); resolve(data.msg);
}, { once: true }); }, { once: true });
bridge.postMessage({ bridge.postMessage({
command: 'vscode/launch-signature', command: signatureName,
data: {} data: {}
}); });
}) })

View File

@ -29,7 +29,7 @@ import { useI18n } from 'vue-i18n';
const { t } = useI18n(); const { t } = useI18n();
import { connectionResult, doConnect, launchConnect } from './connection'; import { connectionResult, doWebConnect, doVscodeConnect } from './connection';
import ConnectionMethod from './connection-method.vue'; import ConnectionMethod from './connection-method.vue';
import ConnectionArgs from './connection-args.vue'; import ConnectionArgs from './connection-args.vue';
@ -47,9 +47,9 @@ async function suitableConnect() {
isLoading.value = true; isLoading.value = true;
if (acquireVsCodeApi === undefined) { if (acquireVsCodeApi === undefined) {
await doConnect(); await doWebConnect({ updateCommandString: false });
} else { } else {
await launchConnect({ updateCommandString: false }); await doVscodeConnect({ updateCommandString: false });
} }
isLoading.value = false; isLoading.value = false;

1
service/.gitignore vendored
View File

@ -26,3 +26,4 @@ setting.json
tabs.example-servers/puppeteer.json tabs.example-servers/puppeteer.json
*.traineddata *.traineddata
.env

View File

@ -3,6 +3,8 @@ import pino from 'pino';
import { routeMessage } from './common/router'; import { routeMessage } from './common/router';
import { VSCodeWebViewLike } from './hook/adapter'; import { VSCodeWebViewLike } from './hook/adapter';
import path from 'node:path';
import * as fs from 'node:fs';
export interface VSCodeMessage { export interface VSCodeMessage {
command: string; command: string;
@ -25,6 +27,73 @@ const logger = pino({
export type MessageHandler = (message: VSCodeMessage) => void; export type MessageHandler = (message: VSCodeMessage) => void;
const wss = new (WebSocket as any).Server({ port: 8080 }); const wss = new (WebSocket as any).Server({ port: 8080 });
interface IStdioLaunchSignature {
type: 'stdio';
commandString: string;
cwd: string;
}
interface ISSELaunchSignature {
type:'sse';
url: string;
oauth: string;
}
export type ILaunchSigature = IStdioLaunchSignature | ISSELaunchSignature;
function refreshConnectionOption(envPath: string) {
const defaultOption = {
type:'stdio',
command: 'mcp',
args: ['run', 'main.py'],
cwd: '../server'
};
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4));
return defaultOption;
}
function getInitConnectionOption() {
const envPath = path.join(__dirname, '..', '.env');
if (!fs.existsSync(envPath)) {
return refreshConnectionOption(envPath);
}
try {
const option = JSON.parse(fs.readFileSync(envPath, 'utf-8'));
return option;
} catch (error) {
logger.error('读取 .env 配置文件');
return refreshConnectionOption(envPath);
}
}
function updateConnectionOption(data: any) {
const envPath = path.join(__dirname, '..', '.env');
if (data.connectionType === 'STDIO') {
const connectionItem = {
type: 'stdio',
command: data.command,
args: data.args,
cwd: data.cwd.replace(/\\/g, '/')
};
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
} else {
const connectionItem = {
type: 'sse',
url: data.url,
oauth: data.oauth
};
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
}
}
wss.on('connection', ws => { wss.on('connection', ws => {
// 仿造 webview 进行统一接口访问 // 仿造 webview 进行统一接口访问
@ -39,11 +108,47 @@ wss.on('connection', ws => {
} }
}); });
const option = getInitConnectionOption();
// 注册消息接受的管线 // 注册消息接受的管线
webview.onDidReceiveMessage(message => { webview.onDidReceiveMessage(message => {
logger.info(`command: [${message.command || 'No Command'}]`); logger.info(`command: [${message.command || 'No Command'}]`);
const { command, data } = message; const { command, data } = message;
routeMessage(command, data, webview);
switch (command) {
case 'web/launch-signature':
const launchResultMessage: ILaunchSigature = option.type === 'stdio' ?
{
type: 'stdio',
commandString: option.command + ' ' + option.args.join(' '),
cwd: option.cwd || ''
} :
{
type: 'sse',
url: option.url,
oauth: option.oauth || ''
};
const launchResult = {
code: 200,
msg: launchResultMessage
};
webview.postMessage({
command: 'web/launch-signature',
data: launchResult
});
break;
case 'web/update-connection-sigature':
updateConnectionOption(data);
break;
default:
routeMessage(command, data, webview);
break;
}
}); });
}); });

1
software/.gitignore vendored
View File

@ -13,4 +13,5 @@ resources
setting.json setting.json
*.traineddata *.traineddata
release release
tabs.*
tabs.example-servers/puppeteer.json tabs.example-servers/puppeteer.json

View File

@ -1,8 +0,0 @@
Remove-Item -Recurse -Force dist
tsc
electron-builder
$dmgFile = Get-ChildItem -Path dist -Filter "OpenMCP-0.0.1.exe"
$dmgFile | ForEach-Object { Write-Host "$($_.FullName) size: $([math]::Round($_.Length / 1MB, 2)) MB" }