push 支持基本的 MCP 项目管理 to 90%

This commit is contained in:
锦恢 2025-04-23 15:29:07 +08:00
parent 4473421708
commit 6c982f1800
9 changed files with 234 additions and 52 deletions

View File

@ -134,3 +134,38 @@ export type APIRequest =
| ResourcesReadRequest
| PromptsGetRequest
| ToolCallRequest;
export interface IStdioConnectionItem {
type: 'stdio';
name: string;
command: string;
args: string[];
cwd?: string;
env?: { [key: string]: string };
filePath?: string;
}
export interface ISSEConnectionItem {
type: 'sse';
name: string;
url: string;
oauth?: string;
env?: { [key: string]: string };
filePath?: string;
}
export interface IStdioLaunchSignature {
type: 'stdio';
commandString: string;
cwd: string;
}
export interface ISSELaunchSignature {
type:'sse';
url: string;
oauth: string;
}
export type IConnectionItem = IStdioConnectionItem | ISSEConnectionItem;
export type ILaunchSigature = IStdioLaunchSignature | ISSELaunchSignature;

View File

@ -2,6 +2,8 @@ import { useMessageBridge } from '@/api/message-bridge';
import { reactive } from 'vue';
import { pinkLog } from '../setting/util';
import { ElMessage } from 'element-plus';
import { ILaunchSigature } from '@/hook/type';
import { url } from 'inspector';
export const connectionMethods = reactive({
current: 'STDIO',
@ -19,6 +21,9 @@ export const connectionMethods = reactive({
export const connectionArgs = reactive({
commandString: '',
cwd: '',
env: {},
oauth: '',
urlString: ''
});
@ -96,7 +101,7 @@ export function doConnect() {
connectionType: 'STDIO',
command: command,
args: commandComponents,
clientName: 'openmcp.connect.stdio.' + command,
clientName: 'openmcp.connect.stdio',
clientVersion: '0.0.1'
}
@ -134,18 +139,39 @@ export async function launchConnect(option: { updateCommandString?: boolean } =
} = option;
connectionMethods.current = 'STDIO';
const bridge = useMessageBridge();
pinkLog('请求启动参数');
const { commandString, cwd } = await getLaunchCommand();
const connectionItem = await getLaunchSignature();
if (connectionItem.type === 'stdio') {
if (updateCommandString) {
connectionArgs.commandString = commandString;
connectionArgs.commandString = connectionItem.commandString;
connectionArgs.cwd = connectionItem.cwd;
connectionArgs.env = {};
if (connectionArgs.commandString.length === 0) {
return;
}
}
await launchStdio();
} else {
if (updateCommandString) {
connectionArgs.urlString = connectionItem.url;
if (connectionArgs.urlString.length === 0) {
return;
}
}
await launchSSE();
}
}
async function launchStdio() {
const bridge = useMessageBridge();
return new Promise<void>((resolve, reject) => {
// 监听 connect
bridge.addCommandListener('connect', async data => {
@ -157,6 +183,27 @@ export async function launchConnect(option: { updateCommandString?: boolean } =
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 command = commandComponents[0];
commandComponents.shift();
const clientStdioConnectionItem = {
serverInfo: connectionResult.serverInfo,
connectionType: 'STDIO',
name: 'openmcp.connect.stdio',
command: command,
args: commandComponents,
cwd: connectionArgs.cwd,
env: connectionArgs.env,
};
bridge.postMessage({
command: 'vscode/update-connection-sigature',
data: JSON.parse(JSON.stringify(clientStdioConnectionItem))
});
} else {
ElMessage({
type: 'error',
@ -176,8 +223,61 @@ export async function launchConnect(option: { updateCommandString?: boolean } =
connectionType: 'STDIO',
command: command,
args: commandComponents,
cwd: cwd,
clientName: 'openmcp.connect.stdio.' + command,
cwd: connectionArgs.cwd,
clientName: 'openmcp.connect.stdio',
clientVersion: '0.0.1'
};
bridge.postMessage({
command: 'connect',
data: connectOption
});
});
}
async function launchSSE() {
const bridge = useMessageBridge();
return new Promise<void>((resolve, reject) => {
// 监听 connect
bridge.addCommandListener('connect', async data => {
const { code, msg } = data;
connectionResult.success = (code === 200);
connectionResult.logString = msg;
if (code === 200) {
const res = await getServerVersion() as { name: string, version: string };
connectionResult.serverInfo.name = res.name || '';
connectionResult.serverInfo.version = res.version || '';
// 同步信息到 vscode
const clientSseConnectionItem = {
serverInfo: connectionResult.serverInfo,
connectionType: 'SSE',
name: 'openmcp.connect.sse',
url: connectionArgs.urlString,
oauth: connectionArgs.oauth,
env: connectionArgs.env
};
bridge.postMessage({
command: 'vscode/update-connection-sigature',
data: JSON.parse(JSON.stringify(clientSseConnectionItem))
});
} else {
ElMessage({
type: 'error',
message: msg
});
}
resolve(void 0);
}, { once: true });
const connectOption: MCPOptions = {
connectionType: 'SSE',
url: connectionArgs.urlString,
clientName: 'openmcp.connect.sse',
clientVersion: '0.0.1'
};
@ -189,19 +289,20 @@ export async function launchConnect(option: { updateCommandString?: boolean } =
}
function getLaunchCommand() {
return new Promise<any>((resolve, reject) => {
function getLaunchSignature() {
return new Promise<ILaunchSigature>((resolve, reject) => {
// 与 vscode 进行同步
const bridge = useMessageBridge();
bridge.addCommandListener('vscode/launch-command', data => {
bridge.addCommandListener('vscode/launch-signature', data => {
pinkLog('收到启动参数');
resolve(data.msg);
}, { once: true });
bridge.postMessage({
command: 'vscode/launch-command',
command: 'vscode/launch-signature',
data: {}
});
})

View File

@ -3,7 +3,6 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import { Implementation } from "@modelcontextprotocol/sdk/types";
import { Writable, Stream } from "node:stream";
// 定义连接类型
type ConnectionType = 'STDIO' | 'SSE';

View File

@ -11,7 +11,7 @@ import { spawnSync } from 'node:child_process';
// TODO: 支持更多的 client
let client: MCPClient | undefined = undefined;
export let client: MCPClient | undefined = undefined;
function tryGetRunCommandError(command: string, args: string[] = [], cwd?: string): string | null {
try {

View File

@ -1,3 +1,5 @@
export { messageController } from './controller';
export { VSCodeWebViewLike } from './adapter';
export { setVscodeWorkspace } from './util';
// TODO: 更加规范
export { client } from './controller';

View File

@ -20,7 +20,7 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('openmcp.sidebar.workspace-connection.revealWebviewPanel', (view: ConnectionViewItem) => {
const item = view.item;
revealOpenMcpWebviewPanel(context, item.name, item);
revealOpenMcpWebviewPanel(context, item.filePath || item.name, item);
})
);

View File

@ -10,6 +10,7 @@ export const panels = new Map<FsPath, vscode.WebviewPanel>();
export interface IStdioConnectionItem {
type: 'stdio';
name: string;
version?: string;
command: string;
args: string[];
cwd?: string;
@ -20,14 +21,31 @@ export interface IStdioConnectionItem {
export interface ISSEConnectionItem {
type: 'sse';
name: string;
version: string;
url: string;
oauth?: string;
env?: { [key: string]: string };
filePath?: string;
}
interface IStdioLaunchSignature {
type: 'stdio';
commandString: string;
cwd: string;
}
interface ISSELaunchSignature {
type:'sse';
url: string;
oauth: string;
}
export type IConnectionItem = IStdioConnectionItem | ISSEConnectionItem;
export type ILaunchSigature = IStdioLaunchSignature | ISSELaunchSignature;
export interface IConnectionConfig {
items: (IStdioConnectionItem | ISSEConnectionItem)[];
items: IConnectionItem[];
}
export const CONNECTION_CONFIG_NAME = 'openmcp_connection.json';
@ -52,7 +70,13 @@ export function getConnectionConfig() {
}
const rawConnectionString = fs.readFileSync(connectionConfig, 'utf-8');
const connection = JSON.parse(rawConnectionString) as IConnectionConfig;
let connection;
try {
connection = JSON.parse(rawConnectionString) as IConnectionConfig;
} catch (error) {
connection = { items: [] };
}
_connectionConfig = connection;
return connection;
}
@ -77,7 +101,13 @@ export function getWorkspaceConnectionConfig() {
}
const rawConnectionString = fs.readFileSync(connectionConfig, 'utf-8');
const connection = JSON.parse(rawConnectionString) as IConnectionConfig;
let connection;
try {
connection = JSON.parse(rawConnectionString) as IConnectionConfig;
} catch (error) {
connection = { items: [] };
}
const workspacePath = getWorkspacePath();
for (const item of connection.items) {
@ -119,8 +149,6 @@ export function saveWorkspaceConnectionConfig(workspace: string) {
interface ClientStdioConnectionItem {
command: string;
args: string[];
clientName: string;
clientVersion: string;
connectionType: 'STDIO';
cwd: string;
env: { [key: string]: string };
@ -128,13 +156,20 @@ interface ClientStdioConnectionItem {
interface ClientSseConnectionItem {
url: string;
clientName: string;
clientVersion: string;
connectionType: 'SSE';
oauth: string;
env: { [key: string]: string };
}
export function updateWorkspaceConnectionConfig(absPath: string, data: ClientStdioConnectionItem | ClientSseConnectionItem) {
interface ServerInfo {
name: string;
version: string;
}
export function updateWorkspaceConnectionConfig(
absPath: string,
data: (ClientStdioConnectionItem | ClientSseConnectionItem) & { serverInfo: ServerInfo }
) {
const connectionItem = getWorkspaceConnectionConfigItemByPath(absPath);
const workspaceConnectionConfig = getWorkspaceConnectionConfig();
@ -148,8 +183,9 @@ export function updateWorkspaceConnectionConfig(absPath: string, data: ClientStd
if (data.connectionType === 'STDIO') {
const connectionItem: IStdioConnectionItem = {
type:'stdio',
name: data.clientName,
type: 'stdio',
name: data.serverInfo.name,
version: data.serverInfo.version,
command: data.command,
args: data.args,
cwd: data.cwd.replace(/\\/g, '/'),
@ -157,6 +193,9 @@ export function updateWorkspaceConnectionConfig(absPath: string, data: ClientStd
filePath: absPath.replace(/\\/g, '/')
};
console.log('get connectionItem: ', connectionItem);
// 插入到第一个
workspaceConnectionConfig.items.unshift(connectionItem);
const workspacePath = getWorkspacePath();
@ -164,11 +203,24 @@ export function updateWorkspaceConnectionConfig(absPath: string, data: ClientStd
vscode.commands.executeCommand('openmcp.sidebar.workspace-connection.refresh');
} else {
const connectionItem: ISSEConnectionItem = {
type: 'sse',
name: data.serverInfo.name,
version: data.serverInfo.version,
url: data.url,
oauth: data.oauth,
filePath: absPath.replace(/\\/g, '/')
};
// 插入到第一个
workspaceConnectionConfig.items.unshift(connectionItem);
const workspacePath = getWorkspacePath();
saveWorkspaceConnectionConfig(workspacePath);
vscode.commands.executeCommand('openmcp.sidebar.workspace-connection.refresh');
}
}
function normaliseConnectionFilePath(item: IStdioConnectionItem | ISSEConnectionItem, workspace: string) {
function normaliseConnectionFilePath(item: IConnectionItem, workspace: string) {
if (item.filePath) {
if (item.filePath.startsWith('{workspace}')) {
return item.filePath.replace('{workspace}', workspace).replace(/\\/g, '/');

View File

@ -1,5 +1,5 @@
import * as vscode from 'vscode';
import { getConnectionConfig, getWorkspaceConnectionConfig, ISSEConnectionItem, IStdioConnectionItem } from './global';
import { getConnectionConfig, getWorkspaceConnectionConfig, IConnectionItem } from './global';
class McpWorkspaceConnectProvider implements vscode.TreeDataProvider<ConnectionViewItem> {
private _onDidChangeTreeData: vscode.EventEmitter<ConnectionViewItem | undefined | null | void> = new vscode.EventEmitter<ConnectionViewItem | undefined | null | void>();
@ -18,7 +18,7 @@ class McpWorkspaceConnectProvider implements vscode.TreeDataProvider<ConnectionV
const connection = getWorkspaceConnectionConfig();
const sidebarItems = connection.items.map((item, index) => {
// 连接的名字
const itemName = this.displayName(item);
const itemName = `${item.name} (${item.type})`
return new ConnectionViewItem(itemName, vscode.TreeItemCollapsibleState.None, item, 'server');
})
@ -26,14 +26,6 @@ class McpWorkspaceConnectProvider implements vscode.TreeDataProvider<ConnectionV
return Promise.resolve(sidebarItems);
}
public displayName(item: IStdioConnectionItem | ISSEConnectionItem) {
if (item.filePath) {
const filename = item.filePath.split('/').pop();
return `${filename} (${item.type})`;
}
return item.name;
}
// 添加 refresh 方法
public refresh(): void {
this._onDidChangeTreeData.fire();
@ -121,7 +113,7 @@ export class ConnectionViewItem extends vscode.TreeItem {
constructor(
public readonly label: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
public readonly item: IStdioConnectionItem | ISSEConnectionItem,
public readonly item: IConnectionItem,
public readonly icon?: string
) {
super(label, collapsibleState);

View File

@ -1,7 +1,7 @@
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as fspath from 'path';
import { getWorkspaceConnectionConfigItemByPath, ISSEConnectionItem, IStdioConnectionItem, panels, updateWorkspaceConnectionConfig } from './global';
import { IConnectionItem, ILaunchSigature, panels, updateWorkspaceConnectionConfig } from './global';
import * as OpenMCPService from '../resources/service';
@ -25,11 +25,10 @@ export function getLaunchCWD(context: vscode.ExtensionContext, uri: vscode.Uri)
return workspaceFolder?.uri.fsPath || '';
}
export function revealOpenMcpWebviewPanel(
context: vscode.ExtensionContext,
panelKey: string,
option: (IStdioConnectionItem | ISSEConnectionItem) = {
option: IConnectionItem = {
type: 'stdio',
name: 'OpenMCP',
command: 'mcp',
@ -68,33 +67,35 @@ export function revealOpenMcpWebviewPanel(
// 拦截消息,注入额外信息
switch (command) {
case 'vscode/launch-command':
const laucnResultMessage = option.type === 'stdio' ?
case 'vscode/launch-signature':
const launchResultMessage: ILaunchSigature = option.type === 'stdio' ?
{
type: 'stdio',
commandString: option.command + ' ' + option.args.join(' '),
cwd: option.cwd
cwd: option.cwd || ''
} :
{
type: 'sse',
url: option.url,
oauth: option.oauth
oauth: option.oauth || ''
};
const launchResult = {
code: 200,
msg: laucnResultMessage
msg: launchResultMessage
};
panel.webview.postMessage({
command: 'vscode/launch-command',
command: 'vscode/launch-signature',
data: launchResult
});
break;
case 'connect':
case 'vscode/update-connection-sigature':
updateWorkspaceConnectionConfig(panelKey, data);
break;
default:
OpenMCPService.messageController(command, data, panel.webview);
break;