push 支持基本的 MCP 项目管理 to 90%
This commit is contained in:
parent
4473421708
commit
6c982f1800
@ -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;
|
||||
|
@ -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: {}
|
||||
});
|
||||
})
|
||||
|
@ -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';
|
||||
|
@ -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 {
|
||||
|
@ -1,3 +1,5 @@
|
||||
export { messageController } from './controller';
|
||||
export { VSCodeWebViewLike } from './adapter';
|
||||
export { setVscodeWorkspace } from './util';
|
||||
// TODO: 更加规范
|
||||
export { client } from './controller';
|
@ -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);
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -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, '/');
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user