0.1.0 完成 vscode 插件端的改造
This commit is contained in:
parent
f37b8babcd
commit
df8b4df2c0
@ -1,5 +1,6 @@
|
||||
import { pinkLog, redLog } from '@/views/setting/util';
|
||||
import { acquireVsCodeApi, electronApi, getPlatform } from './platform';
|
||||
import { isReactive } from 'vue';
|
||||
|
||||
export interface VSCodeMessage {
|
||||
command: string;
|
||||
@ -208,6 +209,21 @@ export class MessageBridge {
|
||||
return () => commandHandlers.delete(wrapperCommandHandler);
|
||||
}
|
||||
|
||||
private deserializeReactiveData(data: any) {
|
||||
if (isReactive(data)) {
|
||||
return JSON.parse(JSON.stringify(data));
|
||||
}
|
||||
|
||||
// 只对第一层进行遍历
|
||||
for (const key in data) {
|
||||
if (isReactive(data[key])) {
|
||||
data[key] = JSON.parse(JSON.stringify(data[key]));
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description do as axios does
|
||||
* @param command
|
||||
@ -215,6 +231,7 @@ export class MessageBridge {
|
||||
* @returns
|
||||
*/
|
||||
public commandRequest<T = any>(command: string, data?: ICommandRequestData): Promise<RestFulResponse<T>> {
|
||||
|
||||
return new Promise<RestFulResponse>((resolve, reject) => {
|
||||
this.addCommandListener(command, (data) => {
|
||||
resolve(data as RestFulResponse);
|
||||
@ -222,7 +239,7 @@ export class MessageBridge {
|
||||
|
||||
this.postMessage({
|
||||
command,
|
||||
data
|
||||
data: this.deserializeReactiveData(data)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -22,8 +22,7 @@ export function getSystemPrompt(name: string) {
|
||||
export async function saveSystemPrompts() {
|
||||
const bridge = useMessageBridge();
|
||||
|
||||
const payload = JSON.parse(JSON.stringify(systemPrompts.value));
|
||||
const res = await bridge.commandRequest('system-prompts/save', { prompts: payload });
|
||||
const res = await bridge.commandRequest('system-prompts/save', { prompts: systemPrompts.value });
|
||||
if (res.code === 200) {
|
||||
pinkLog('system prompt 保存成功');
|
||||
}
|
||||
|
@ -84,7 +84,11 @@ const handleKeydown = (event: KeyboardEvent) => {
|
||||
const copy = async () => {
|
||||
try {
|
||||
if (navigator.clipboard) {
|
||||
await navigator.clipboard.writeText(userInput.value);
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
'text/plain': new Blob([userInput.value], { type: 'text/plain' })
|
||||
})
|
||||
]);
|
||||
} else {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = userInput.value;
|
||||
|
@ -145,7 +145,6 @@ async function connect() {
|
||||
}
|
||||
|
||||
.connect-action {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
@ -367,7 +367,9 @@ export class McpClient {
|
||||
*/
|
||||
public async lookupEnvVar(varNames: string[]) {
|
||||
const bridge = useMessageBridge();
|
||||
const { code, msg } = await bridge.commandRequest('lookup-env-var', { keys: varNames });
|
||||
const { code, msg } = await bridge.commandRequest('lookup-env-var', {
|
||||
keys: varNames
|
||||
});
|
||||
|
||||
if (code === 200) {
|
||||
this.connectionResult.logString.push({
|
||||
|
@ -20,6 +20,7 @@ export function createTest(call: ToolCall) {
|
||||
tab.name = t("tools");
|
||||
|
||||
const storage: ToolStorage = {
|
||||
activeNames: [0],
|
||||
currentToolName: call.function.name,
|
||||
formData: JSON.parse(call.function.arguments)
|
||||
};
|
||||
|
@ -2,4 +2,4 @@ export { routeMessage } from './common/router';
|
||||
export { VSCodeWebViewLike, TaskLoopAdapter } from './hook/adapter';
|
||||
export { setVscodeWorkspace, setRunningCWD } from './hook/setting';
|
||||
// TODO: 更加规范
|
||||
export { client } from './mcp/connect.service';
|
||||
export { clientMap } from './mcp/connect.service';
|
@ -38,7 +38,7 @@ function refreshConnectionOption(envPath: string) {
|
||||
|
||||
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4));
|
||||
|
||||
return { data: [ defaultOption ] };
|
||||
return { items: [ defaultOption ] };
|
||||
}
|
||||
|
||||
function acquireConnectionOption() {
|
||||
@ -51,16 +51,16 @@ function acquireConnectionOption() {
|
||||
try {
|
||||
const option = JSON.parse(fs.readFileSync(envPath, 'utf-8'));
|
||||
|
||||
if (!option.data) {
|
||||
if (!option.items) {
|
||||
return refreshConnectionOption(envPath);
|
||||
}
|
||||
|
||||
if (option.data && option.data.length === 0) {
|
||||
if (option.items && option.items.length === 0) {
|
||||
return refreshConnectionOption(envPath);
|
||||
}
|
||||
|
||||
// 按照前端的规范,整理成 commandString 样式
|
||||
option.data = option.data.map((item: any) => {
|
||||
option.items = option.items.map((item: any) => {
|
||||
if (item.connectionType === 'STDIO') {
|
||||
item.commandString = [item.command, ...item.args]?.join(' ');
|
||||
} else {
|
||||
@ -80,7 +80,7 @@ function acquireConnectionOption() {
|
||||
|
||||
function updateConnectionOption(data: any) {
|
||||
const envPath = path.join(__dirname, '..', '.env');
|
||||
const connection = { data };
|
||||
const connection = { items: data };
|
||||
fs.writeFileSync(envPath, JSON.stringify(connection, null, 4));
|
||||
}
|
||||
|
||||
@ -117,7 +117,7 @@ wss.on('connection', (ws: any) => {
|
||||
case 'web/launch-signature':
|
||||
const launchResult = {
|
||||
code: 200,
|
||||
msg: option.data
|
||||
msg: option.items
|
||||
};
|
||||
|
||||
webview.postMessage({
|
||||
|
@ -16,8 +16,22 @@ export class ConnectController {
|
||||
const { keys } = data;
|
||||
const values = keys.map((key: string) => {
|
||||
// TODO: 在 Windows 上测试
|
||||
if (process.platform === 'win32' && key.toLowerCase() === 'path') {
|
||||
key = 'Path'; // 确保正确匹配环境变量的 ke
|
||||
console.log(key);
|
||||
console.log(process.env);
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
switch (key) {
|
||||
case 'USER':
|
||||
return process.env.USERNAME || '';
|
||||
case 'HOME':
|
||||
return process.env.USERPROFILE || process.env.HOME;
|
||||
case 'LOGNAME':
|
||||
return process.env.USERNAME || '';
|
||||
case 'SHELL':
|
||||
return process.env.SHELL || process.env.COMSPEC;
|
||||
case 'TERM':
|
||||
return process.env.TERM || '未设置 (Windows 默认终端)';
|
||||
}
|
||||
}
|
||||
|
||||
return process.env[key] || '';
|
||||
|
@ -5,7 +5,7 @@ import { createOcrWorker, saveBase64ImageData } from "./ocr.service";
|
||||
|
||||
export class OcrController {
|
||||
@Controller('ocr/get-ocr-image')
|
||||
async getOcrImage(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
async getOcrImage(data: any, webview: PostMessageble) {
|
||||
const { filename } = data;
|
||||
const buffer = diskStorage.getSync(filename);
|
||||
const base64String = buffer ? buffer.toString('base64'): undefined;
|
||||
@ -18,7 +18,7 @@ export class OcrController {
|
||||
}
|
||||
|
||||
@Controller('ocr/start-ocr')
|
||||
async startOcr(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
async startOcr(data: any, webview: PostMessageble) {
|
||||
const { base64String, mimeType } = data;
|
||||
|
||||
const filename = saveBase64ImageData(base64String, mimeType);
|
||||
|
@ -39,7 +39,7 @@ function refreshConnectionOption(envPath: string) {
|
||||
|
||||
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4));
|
||||
|
||||
return { data: [defaultOption] };
|
||||
return { items: [defaultOption] };
|
||||
}
|
||||
|
||||
function acquireConnectionOption() {
|
||||
@ -52,16 +52,16 @@ function acquireConnectionOption() {
|
||||
try {
|
||||
const option = JSON.parse(fs.readFileSync(envPath, 'utf-8'));
|
||||
|
||||
if (!option.data) {
|
||||
if (!option.items) {
|
||||
return refreshConnectionOption(envPath);
|
||||
}
|
||||
|
||||
if (option.data && option.data.length === 0) {
|
||||
if (option.items && option.items.length === 0) {
|
||||
return refreshConnectionOption(envPath);
|
||||
}
|
||||
|
||||
// 按照前端的规范,整理成 commandString 样式
|
||||
option.data = option.data.map((item: any) => {
|
||||
option.items = option.items.map((item: any) => {
|
||||
if (item.connectionType === 'STDIO') {
|
||||
item.commandString = [item.command, ...item.args]?.join(' ');
|
||||
} else {
|
||||
@ -88,7 +88,7 @@ const authPassword = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '.env
|
||||
|
||||
function updateConnectionOption(data: any) {
|
||||
const envPath = path.join(__dirname, '..', '.env');
|
||||
const connection = { data };
|
||||
const connection = { items: data };
|
||||
fs.writeFileSync(envPath, JSON.stringify(connection, null, 4));
|
||||
}
|
||||
|
||||
@ -147,7 +147,7 @@ wss.on('connection', (ws: any) => {
|
||||
case 'web/launch-signature':
|
||||
const launchResult = {
|
||||
code: 200,
|
||||
msg: option.data
|
||||
msg: option.items
|
||||
};
|
||||
|
||||
webview.postMessage({
|
||||
|
217
src/global.ts
217
src/global.ts
@ -6,47 +6,43 @@ import * as fs from 'fs';
|
||||
export type FsPath = string;
|
||||
export const panels = new Map<FsPath, vscode.WebviewPanel>();
|
||||
|
||||
export interface IStdioConnectionItem {
|
||||
type: 'STDIO';
|
||||
name: string;
|
||||
version?: string;
|
||||
command: string;
|
||||
args: string[];
|
||||
cwd?: string;
|
||||
env?: { [key: string]: string };
|
||||
filePath?: string;
|
||||
}
|
||||
|
||||
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: IConnectionItem[];
|
||||
items: (McpOptions[] | McpOptions)[];
|
||||
}
|
||||
|
||||
export type ConnectionType = 'STDIO' | 'SSE' | 'STREAMABLE_HTTP';
|
||||
|
||||
export interface McpOptions {
|
||||
connectionType: ConnectionType;
|
||||
command?: string;
|
||||
|
||||
// STDIO 特定选项
|
||||
args?: string[];
|
||||
cwd?: string;
|
||||
env?: Record<string, string>;
|
||||
|
||||
// SSE 特定选项
|
||||
url?: string;
|
||||
oauth?: any;
|
||||
|
||||
// 通用客户端选项
|
||||
clientName?: string;
|
||||
clientVersion?: string;
|
||||
serverInfo?: {
|
||||
name: string
|
||||
version: string
|
||||
}
|
||||
|
||||
// vscode 专用
|
||||
filePath?: string;
|
||||
name?: string;
|
||||
version?: string;
|
||||
type?: ConnectionType;
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
|
||||
export const CONNECTION_CONFIG_NAME = 'openmcp_connection.json';
|
||||
|
||||
let _connectionConfig: IConnectionConfig | undefined;
|
||||
@ -119,11 +115,14 @@ export function getWorkspaceConnectionConfig() {
|
||||
}
|
||||
|
||||
const workspacePath = getWorkspacePath();
|
||||
for (const item of connection.items) {
|
||||
for (let item of connection.items) {
|
||||
item = Array.isArray(item) ? item[0] : item;
|
||||
const itemType = item.type || item.connectionType;
|
||||
|
||||
if (item.filePath && item.filePath.startsWith('{workspace}')) {
|
||||
item.filePath = item.filePath.replace('{workspace}', workspacePath).replace(/\\/g, '/');
|
||||
}
|
||||
if (item.type === 'STDIO' && item.cwd && item.cwd.startsWith('{workspace}')) {
|
||||
if (itemType === 'STDIO' && item.cwd && item.cwd.startsWith('{workspace}')) {
|
||||
item.cwd = item.cwd.replace('{workspace}', workspacePath).replace(/\\/g, '/');
|
||||
}
|
||||
}
|
||||
@ -165,40 +164,26 @@ export function saveWorkspaceConnectionConfig(workspace: string) {
|
||||
const connectionConfigPath = fspath.join(configDir, CONNECTION_CONFIG_NAME);
|
||||
|
||||
const workspacePath = getWorkspacePath();
|
||||
for (const item of connectionConfig.items) {
|
||||
for (let item of connectionConfig.items) {
|
||||
|
||||
item = Array.isArray(item) ? item[0] : item;
|
||||
const itemType = item.type || item.connectionType;
|
||||
item.type = undefined;
|
||||
|
||||
if (item.filePath && item.filePath.replace(/\\/g, '/').startsWith(workspacePath)) {
|
||||
item.filePath = item.filePath.replace(workspacePath, '{workspace}').replace(/\\/g, '/');
|
||||
}
|
||||
if (item.type ==='STDIO' && item.cwd && item.cwd.replace(/\\/g, '/').startsWith(workspacePath)) {
|
||||
if (item.type === 'STDIO' && item.cwd && item.cwd.replace(/\\/g, '/').startsWith(workspacePath)) {
|
||||
item.cwd = item.cwd.replace(workspacePath, '{workspace}').replace(/\\/g, '/');
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(connectionConfigPath, JSON.stringify(connectionConfig, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
interface ClientStdioConnectionItem {
|
||||
command: string;
|
||||
args: string[];
|
||||
connectionType: 'STDIO';
|
||||
cwd: string;
|
||||
env: { [key: string]: string };
|
||||
}
|
||||
|
||||
interface ClientSseConnectionItem {
|
||||
url: string;
|
||||
connectionType: 'SSE';
|
||||
oauth: string;
|
||||
env: { [key: string]: string };
|
||||
}
|
||||
|
||||
interface ServerInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export function updateWorkspaceConnectionConfig(
|
||||
absPath: string,
|
||||
data: (ClientStdioConnectionItem | ClientSseConnectionItem) & { serverInfo: ServerInfo }
|
||||
data: McpOptions[]
|
||||
) {
|
||||
const connectionItem = getWorkspaceConnectionConfigItemByPath(absPath);
|
||||
const workspaceConnectionConfig = getWorkspaceConnectionConfig();
|
||||
@ -211,48 +196,29 @@ export function updateWorkspaceConnectionConfig(
|
||||
}
|
||||
}
|
||||
|
||||
if (data.connectionType === 'STDIO') {
|
||||
const connectionItem: IStdioConnectionItem = {
|
||||
type: 'STDIO',
|
||||
name: data.serverInfo.name,
|
||||
version: data.serverInfo.version,
|
||||
command: data.command,
|
||||
args: data.args,
|
||||
cwd: data.cwd.replace(/\\/g, '/'),
|
||||
env: data.env,
|
||||
filePath: absPath.replace(/\\/g, '/')
|
||||
};
|
||||
// 对于第一个 item 添加 filePath
|
||||
// 对路径进行标准化
|
||||
data.forEach(item => {
|
||||
item.filePath = absPath.replace(/\\/g, '/');
|
||||
item.cwd = item.cwd?.replace(/\\/g, '/');
|
||||
item.name = item.serverInfo?.name;
|
||||
item.version = item.serverInfo?.version;
|
||||
item.type = undefined;
|
||||
});
|
||||
|
||||
console.log('get connectionItem: ', connectionItem);
|
||||
console.log('get connectionItem: ', data);
|
||||
|
||||
// 插入到第一个
|
||||
workspaceConnectionConfig.items.unshift(data);
|
||||
const workspacePath = getWorkspacePath();
|
||||
saveWorkspaceConnectionConfig(workspacePath);
|
||||
vscode.commands.executeCommand('openmcp.sidebar.workspace-connection.refresh');
|
||||
|
||||
// 插入到第一个
|
||||
workspaceConnectionConfig.items.unshift(connectionItem);
|
||||
const workspacePath = getWorkspacePath();
|
||||
saveWorkspaceConnectionConfig(workspacePath);
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
export function updateInstalledConnectionConfig(
|
||||
absPath: string,
|
||||
data: (ClientStdioConnectionItem | ClientSseConnectionItem) & { serverInfo: ServerInfo }
|
||||
data: McpOptions[]
|
||||
) {
|
||||
const connectionItem = getInstalledConnectionConfigItemByPath(absPath);
|
||||
const installedConnectionConfig = getConnectionConfig();
|
||||
@ -265,45 +231,26 @@ export function updateInstalledConnectionConfig(
|
||||
}
|
||||
}
|
||||
|
||||
if (data.connectionType === 'STDIO') {
|
||||
const connectionItem: IStdioConnectionItem = {
|
||||
type: 'STDIO',
|
||||
name: data.serverInfo.name,
|
||||
version: data.serverInfo.version,
|
||||
command: data.command,
|
||||
args: data.args,
|
||||
cwd: data.cwd.replace(/\\/g, '/'),
|
||||
env: data.env,
|
||||
filePath: absPath.replace(/\\/g, '/')
|
||||
};
|
||||
// 对于第一个 item 添加 filePath
|
||||
// 对路径进行标准化
|
||||
data.forEach(item => {
|
||||
item.filePath = absPath.replace(/\\/g, '/');
|
||||
item.cwd = item.cwd?.replace(/\\/g, '/');
|
||||
item.name = item.serverInfo?.name;
|
||||
item.version = item.serverInfo?.version;
|
||||
item.type = undefined;
|
||||
});
|
||||
|
||||
console.log('get connectionItem: ', connectionItem);
|
||||
console.log('get connectionItem: ', data);
|
||||
|
||||
|
||||
// 插入到第一个
|
||||
installedConnectionConfig.items.unshift(connectionItem);
|
||||
saveConnectionConfig();
|
||||
vscode.commands.executeCommand('openmcp.sidebar.installed-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, '/')
|
||||
};
|
||||
|
||||
// 插入到第一个
|
||||
installedConnectionConfig.items.unshift(connectionItem);
|
||||
saveConnectionConfig();
|
||||
vscode.commands.executeCommand('openmcp.sidebar.installed-connection.refresh');
|
||||
}
|
||||
// 插入到第一个
|
||||
installedConnectionConfig.items.unshift(data);
|
||||
saveConnectionConfig();
|
||||
vscode.commands.executeCommand('openmcp.sidebar.installed-connection.refresh');
|
||||
}
|
||||
|
||||
|
||||
function normaliseConnectionFilePath(item: IConnectionItem, workspace: string) {
|
||||
function normaliseConnectionFilePath(item: McpOptions, workspace: string) {
|
||||
if (item.filePath) {
|
||||
if (item.filePath.startsWith('{workspace}')) {
|
||||
return item.filePath.replace('{workspace}', workspace).replace(/\\/g, '/');
|
||||
@ -329,7 +276,9 @@ export function getWorkspaceConnectionConfigItemByPath(absPath: string) {
|
||||
const workspaceConnectionConfig = getWorkspaceConnectionConfig();
|
||||
|
||||
const normaliseAbsPath = absPath.replace(/\\/g, '/');
|
||||
for (const item of workspaceConnectionConfig.items) {
|
||||
for (let item of workspaceConnectionConfig.items) {
|
||||
item = Array.isArray(item)? item[0] : item;
|
||||
|
||||
const filePath = normaliseConnectionFilePath(item, workspacePath);
|
||||
if (filePath === normaliseAbsPath) {
|
||||
return item;
|
||||
@ -347,7 +296,9 @@ export function getInstalledConnectionConfigItemByPath(absPath: string) {
|
||||
const installedConnectionConfig = getConnectionConfig();
|
||||
|
||||
const normaliseAbsPath = absPath.replace(/\\/g, '/');
|
||||
for (const item of installedConnectionConfig.items) {
|
||||
for (let item of installedConnectionConfig.items) {
|
||||
item = Array.isArray(item)? item[0] : item;
|
||||
|
||||
const filePath = (item.filePath || '').replace(/\\/g, '/');
|
||||
if (filePath === normaliseAbsPath) {
|
||||
return item;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as vscode from 'vscode';
|
||||
import type { IConnectionItem } from '../global';
|
||||
import { McpOptions } from '../global';
|
||||
|
||||
export class SidebarItem extends vscode.TreeItem {
|
||||
constructor(
|
||||
@ -18,7 +18,7 @@ export class ConnectionViewItem extends vscode.TreeItem {
|
||||
constructor(
|
||||
public readonly label: string,
|
||||
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
|
||||
public readonly item: IConnectionItem,
|
||||
public readonly item: McpOptions[] | McpOptions,
|
||||
public readonly icon?: string
|
||||
) {
|
||||
super(label, collapsibleState);
|
||||
|
@ -22,6 +22,7 @@ export class McpInstalledConnectProvider implements vscode.TreeDataProvider<Conn
|
||||
const connection = getConnectionConfig();
|
||||
const sidebarItems = connection.items.map((item, index) => {
|
||||
// 连接的名字
|
||||
item = Array.isArray(item)? item[0] : item;
|
||||
const itemName = `${item.name} (${item.type})`
|
||||
return new ConnectionViewItem(itemName, vscode.TreeItemCollapsibleState.None, item, 'server');
|
||||
})
|
||||
@ -33,7 +34,9 @@ export class McpInstalledConnectProvider implements vscode.TreeDataProvider<Conn
|
||||
@RegisterCommand('revealWebviewPanel')
|
||||
public revealWebviewPanel(context: vscode.ExtensionContext, view: ConnectionViewItem) {
|
||||
const item = view.item;
|
||||
revealOpenMcpWebviewPanel(context, 'installed', item.filePath || item.name, item);
|
||||
const masterNode = Array.isArray(item)? item[0] : item;
|
||||
const name = masterNode.filePath || masterNode.name || '';
|
||||
revealOpenMcpWebviewPanel(context, 'installed', name, item);
|
||||
}
|
||||
|
||||
@RegisterCommand('refresh')
|
||||
@ -45,7 +48,7 @@ export class McpInstalledConnectProvider implements vscode.TreeDataProvider<Conn
|
||||
public async addConnection(context: vscode.ExtensionContext) {
|
||||
const item = await acquireInstalledConnection();
|
||||
|
||||
if (!item) {
|
||||
if (item.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { getConnectionConfig, IConnectionItem, panels, saveConnectionConfig, getFirstValidPathFromCommand } from "../global";
|
||||
import { getConnectionConfig, panels, saveConnectionConfig, getFirstValidPathFromCommand, McpOptions } from "../global";
|
||||
import { exec, spawn } from 'node:child_process';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export async function deleteInstalledConnection(item: IConnectionItem) {
|
||||
export async function deleteInstalledConnection(item: McpOptions[] | McpOptions) {
|
||||
// 弹出确认对话框
|
||||
const masterNode = Array.isArray(item) ? item[0] : item;
|
||||
const name = masterNode.name;
|
||||
const confirm = await vscode.window.showWarningMessage(
|
||||
`确定要删除连接 "${item.name}" 吗?`,
|
||||
`确定要删除连接 "${name}" 吗?`,
|
||||
{ modal: true },
|
||||
'确定'
|
||||
);
|
||||
@ -26,12 +28,12 @@ export async function deleteInstalledConnection(item: IConnectionItem) {
|
||||
|
||||
// 刷新侧边栏视图
|
||||
vscode.commands.executeCommand('openmcp.sidebar.installed-connection.refresh');
|
||||
panels.delete(item.name);
|
||||
|
||||
// 如果该连接有对应的webview面板,则关闭它
|
||||
if (panels.has(item.filePath || item.name)) {
|
||||
const panel = panels.get(item.filePath || item.name);
|
||||
panel?.dispose();
|
||||
}
|
||||
const filePath = masterNode.filePath || '';
|
||||
const panel = panels.get(filePath);
|
||||
panel?.dispose();
|
||||
panels.delete(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,15 +52,15 @@ export async function validateAndGetCommandPath(commandString: string, cwd?: str
|
||||
}
|
||||
}
|
||||
|
||||
export async function acquireInstalledConnection(): Promise<IConnectionItem | undefined> {
|
||||
export async function acquireInstalledConnection(): Promise<McpOptions[]> {
|
||||
// 让用户选择连接类型
|
||||
const connectionType = await vscode.window.showQuickPick(['STDIO', 'SSE'], {
|
||||
const connectionType = await vscode.window.showQuickPick(['STDIO', 'SSE', 'STREAMABLE_HTTP'], {
|
||||
placeHolder: '请选择连接类型',
|
||||
canPickMany: false
|
||||
});
|
||||
|
||||
if (!connectionType) {
|
||||
return; // 用户取消选择
|
||||
return []; // 用户取消选择
|
||||
}
|
||||
|
||||
if (connectionType === 'STDIO') {
|
||||
@ -69,7 +71,7 @@ export async function acquireInstalledConnection(): Promise<IConnectionItem | un
|
||||
});
|
||||
|
||||
if (!commandString) {
|
||||
return; // 用户取消输入
|
||||
return []; // 用户取消输入
|
||||
}
|
||||
|
||||
// 获取 cwd
|
||||
@ -84,7 +86,7 @@ export async function acquireInstalledConnection(): Promise<IConnectionItem | un
|
||||
console.log('Command Path:', commandPath);
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(`无效的 command: ${error}`);
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
const commands = commandString.split(' ');
|
||||
@ -96,24 +98,24 @@ export async function acquireInstalledConnection(): Promise<IConnectionItem | un
|
||||
const filePath = await getFirstValidPathFromCommand(commandString, cwd || '');
|
||||
|
||||
// 保存连接配置
|
||||
return {
|
||||
type: 'STDIO',
|
||||
return [{
|
||||
connectionType: 'STDIO',
|
||||
name: `STDIO-${Date.now()}`,
|
||||
command: command,
|
||||
args,
|
||||
cwd: cwd || '',
|
||||
filePath: filePath,
|
||||
};
|
||||
}];
|
||||
|
||||
} else if (connectionType === 'SSE') {
|
||||
// 获取 url
|
||||
const url = await vscode.window.showInputBox({
|
||||
prompt: '请输入连接的 URL',
|
||||
placeHolder: '例如: https://127.0.0.1:8282'
|
||||
placeHolder: '例如: https://127.0.0.1:8282/sse'
|
||||
});
|
||||
|
||||
if (!url) {
|
||||
return; // 用户取消输入
|
||||
return []; // 用户取消输入
|
||||
}
|
||||
|
||||
// 获取 oauth
|
||||
@ -123,13 +125,40 @@ export async function acquireInstalledConnection(): Promise<IConnectionItem | un
|
||||
});
|
||||
|
||||
// 保存连接配置
|
||||
return {
|
||||
type: 'SSE',
|
||||
return [{
|
||||
connectionType: 'SSE',
|
||||
name: `SSE-${Date.now()}`,
|
||||
version: '1.0', // 假设默认版本为 1.0,可根据实际情况修改
|
||||
url: url,
|
||||
oauth: oauth || ''
|
||||
}];
|
||||
} else if (connectionType === 'STREAMABLE_HTTP') {
|
||||
// 获取 url
|
||||
const url = await vscode.window.showInputBox({
|
||||
prompt: '请输入连接的 URL',
|
||||
placeHolder: '例如: https://127.0.0.1:8282/stream'
|
||||
});
|
||||
|
||||
if (!url) {
|
||||
return []; // 用户取消输入
|
||||
}
|
||||
|
||||
// 获取 oauth
|
||||
const oauth = await vscode.window.showInputBox({
|
||||
prompt: '请输入 OAuth 令牌,可选',
|
||||
placeHolder: '例如: your-oauth-token'
|
||||
});
|
||||
|
||||
// 保存连接配置
|
||||
return [{
|
||||
connectionType: 'STREAMABLE_HTTP',
|
||||
name: `STREAMABLE_HTTP-${Date.now()}`,
|
||||
version: '1.0', // 假设默认版本为 1.0,可根据实际情况修改
|
||||
url: url,
|
||||
oauth: oauth || ''
|
||||
}];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ export class McpWorkspaceConnectProvider implements vscode.TreeDataProvider<Conn
|
||||
const connection = getWorkspaceConnectionConfig();
|
||||
const sidebarItems = connection.items.map((item, index) => {
|
||||
// 连接的名字
|
||||
item = Array.isArray(item) ? item[0] : item;
|
||||
const itemName = `${item.name} (${item.type})`
|
||||
return new ConnectionViewItem(itemName, vscode.TreeItemCollapsibleState.None, item, 'server');
|
||||
})
|
||||
@ -33,7 +34,9 @@ export class McpWorkspaceConnectProvider implements vscode.TreeDataProvider<Conn
|
||||
@RegisterCommand('revealWebviewPanel')
|
||||
public revealWebviewPanel(context: vscode.ExtensionContext, view: ConnectionViewItem) {
|
||||
const item = view.item;
|
||||
revealOpenMcpWebviewPanel(context, 'workspace', item.filePath || item.name, item);
|
||||
const masterNode = Array.isArray(item)? item[0] : item;
|
||||
const name = masterNode.filePath || masterNode.name || '';
|
||||
revealOpenMcpWebviewPanel(context, 'workspace', name, item);
|
||||
}
|
||||
|
||||
@RegisterCommand('refresh')
|
||||
|
@ -1,92 +1,12 @@
|
||||
import { getFirstValidPathFromCommand, getWorkspaceConnectionConfig, getWorkspacePath, IConnectionItem, panels, saveWorkspaceConnectionConfig } from "../global";
|
||||
|
||||
import { getFirstValidPathFromCommand, getWorkspaceConnectionConfig, getWorkspacePath, McpOptions, panels, saveWorkspaceConnectionConfig } from "../global";
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
|
||||
|
||||
export async function acquireUserCustomConnection(): Promise<IConnectionItem | undefined> {
|
||||
// 让用户选择连接类型
|
||||
const connectionType = await vscode.window.showQuickPick(['STDIO', 'SSE'], {
|
||||
placeHolder: '请选择连接类型'
|
||||
});
|
||||
|
||||
if (!connectionType) {
|
||||
return; // 用户取消选择
|
||||
}
|
||||
|
||||
if (connectionType === 'STDIO') {
|
||||
// 获取 command
|
||||
const commandString = await vscode.window.showInputBox({
|
||||
prompt: '请输入连接的 command',
|
||||
placeHolder: '例如: mcp run main.py'
|
||||
});
|
||||
|
||||
if (!commandString) {
|
||||
return; // 用户取消输入
|
||||
}
|
||||
|
||||
// 获取 cwd
|
||||
const cwd = await vscode.window.showInputBox({
|
||||
prompt: '请输入工作目录 (cwd),可选',
|
||||
placeHolder: '例如: /path/to/project'
|
||||
});
|
||||
|
||||
// 校验 command + cwd 是否有效
|
||||
try {
|
||||
const commandPath = await validateAndGetCommandPath(commandString, cwd);
|
||||
console.log('Command Path:', commandPath);
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(`无效的 command: ${error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const commands = commandString.split(' ');
|
||||
const command = commands[0];
|
||||
const args = commands.slice(1);
|
||||
const filePath = await getFirstValidPathFromCommand(commandString, cwd || '');
|
||||
|
||||
// 保存连接配置
|
||||
return {
|
||||
type: 'STDIO',
|
||||
name: `STDIO-${Date.now()}`,
|
||||
command: command,
|
||||
args,
|
||||
cwd: cwd || '',
|
||||
filePath
|
||||
};
|
||||
|
||||
} else if (connectionType === 'SSE') {
|
||||
// 获取 url
|
||||
const url = await vscode.window.showInputBox({
|
||||
prompt: '请输入连接的 URL',
|
||||
placeHolder: '例如: https://127.0.0.1:8282'
|
||||
});
|
||||
|
||||
if (!url) {
|
||||
return; // 用户取消输入
|
||||
}
|
||||
|
||||
// 获取 oauth
|
||||
const oauth = await vscode.window.showInputBox({
|
||||
prompt: '请输入 OAuth 令牌,可选',
|
||||
placeHolder: '例如: your-oauth-token'
|
||||
});
|
||||
|
||||
// 保存连接配置
|
||||
return {
|
||||
type: 'SSE',
|
||||
name: `SSE-${Date.now()}`,
|
||||
version: '1.0', // 假设默认版本为 1.0,可根据实际情况修改
|
||||
url: url,
|
||||
oauth: oauth || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteUserConnection(item: IConnectionItem) {
|
||||
export async function deleteUserConnection(item: McpOptions[] | McpOptions) {
|
||||
// 弹出确认对话框
|
||||
const masterNode = Array.isArray(item) ? item[0] : item;
|
||||
const name = masterNode.name;
|
||||
const confirm = await vscode.window.showWarningMessage(
|
||||
`确定要删除连接 "${item.name}" 吗?`,
|
||||
`确定要删除连接 "${name}" 吗?`,
|
||||
{ modal: true },
|
||||
'确定'
|
||||
);
|
||||
@ -108,12 +28,12 @@ export async function deleteUserConnection(item: IConnectionItem) {
|
||||
|
||||
// 刷新侧边栏视图
|
||||
vscode.commands.executeCommand('openmcp.sidebar.workspace-connection.refresh');
|
||||
panels.delete(item.name);
|
||||
|
||||
// 如果该连接有对应的webview面板,则关闭它
|
||||
if (panels.has(item.filePath || item.name)) {
|
||||
const panel = panels.get(item.filePath || item.name);
|
||||
panel?.dispose();
|
||||
}
|
||||
const filePath = masterNode.filePath || '';
|
||||
const panel = panels.get(filePath);
|
||||
panel?.dispose();
|
||||
panels.delete(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,3 +49,109 @@ export async function validateAndGetCommandPath(command: string, cwd?: string):
|
||||
throw new Error(`无法找到命令: ${command.split(' ')[0]}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function acquireUserCustomConnection(): Promise<McpOptions[]> {
|
||||
// 让用户选择连接类型
|
||||
const connectionType = await vscode.window.showQuickPick(['STDIO', 'SSE'], {
|
||||
placeHolder: '请选择连接类型'
|
||||
});
|
||||
|
||||
if (!connectionType) {
|
||||
return []; // 用户取消选择
|
||||
}
|
||||
|
||||
if (connectionType === 'STDIO') {
|
||||
// 获取 command
|
||||
const commandString = await vscode.window.showInputBox({
|
||||
prompt: '请输入连接的 command',
|
||||
placeHolder: '例如: mcp run main.py'
|
||||
});
|
||||
|
||||
if (!commandString) {
|
||||
return []; // 用户取消输入
|
||||
}
|
||||
|
||||
// 获取 cwd
|
||||
const cwd = await vscode.window.showInputBox({
|
||||
prompt: '请输入工作目录 (cwd),可选',
|
||||
placeHolder: '例如: /path/to/project'
|
||||
});
|
||||
|
||||
// 校验 command + cwd 是否有效
|
||||
try {
|
||||
const commandPath = await validateAndGetCommandPath(commandString, cwd);
|
||||
console.log('Command Path:', commandPath);
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(`无效的 command: ${error}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const commands = commandString.split(' ');
|
||||
const command = commands[0];
|
||||
const args = commands.slice(1);
|
||||
const filePath = await getFirstValidPathFromCommand(commandString, cwd || '');
|
||||
|
||||
// 保存连接配置
|
||||
return [{
|
||||
connectionType: 'STDIO',
|
||||
name: `STDIO-${Date.now()}`,
|
||||
command: command,
|
||||
args,
|
||||
cwd: cwd || '',
|
||||
filePath
|
||||
}];
|
||||
|
||||
} else if (connectionType === 'SSE') {
|
||||
// 获取 url
|
||||
const url = await vscode.window.showInputBox({
|
||||
prompt: '请输入连接的 URL',
|
||||
placeHolder: '例如: https://127.0.0.1:8282/sse'
|
||||
});
|
||||
|
||||
if (!url) {
|
||||
return []; // 用户取消输入
|
||||
}
|
||||
|
||||
// 获取 oauth
|
||||
const oauth = await vscode.window.showInputBox({
|
||||
prompt: '请输入 OAuth 令牌,可选',
|
||||
placeHolder: '例如: your-oauth-token'
|
||||
});
|
||||
|
||||
// 保存连接配置
|
||||
return [{
|
||||
connectionType: 'SSE',
|
||||
name: `SSE-${Date.now()}`,
|
||||
version: '1.0', // 假设默认版本为 1.0,可根据实际情况修改
|
||||
url: url,
|
||||
oauth: oauth || ''
|
||||
}];
|
||||
} else if (connectionType === 'STREAMABLE_HTTP') {
|
||||
// 获取 url
|
||||
const url = await vscode.window.showInputBox({
|
||||
prompt: '请输入连接的 URL',
|
||||
placeHolder: '例如: https://127.0.0.1:8282/stream'
|
||||
});
|
||||
|
||||
if (!url) {
|
||||
return []; // 用户取消输入
|
||||
}
|
||||
|
||||
// 获取 oauth
|
||||
const oauth = await vscode.window.showInputBox({
|
||||
prompt: '请输入 OAuth 令牌,可选',
|
||||
placeHolder: '例如: your-oauth-token'
|
||||
});
|
||||
|
||||
// 保存连接配置
|
||||
return [{
|
||||
connectionType: 'STREAMABLE_HTTP',
|
||||
name: `STREAMABLE_HTTP-${Date.now()}`,
|
||||
version: '1.0', // 假设默认版本为 1.0,可根据实际情况修改
|
||||
url: url,
|
||||
oauth: oauth || ''
|
||||
}];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
@ -21,7 +21,7 @@ export class WebviewController {
|
||||
}
|
||||
|
||||
revealOpenMcpWebviewPanel(context, 'workspace', uri.fsPath, {
|
||||
type: 'STDIO',
|
||||
connectionType: 'STDIO',
|
||||
name: 'OpenMCP',
|
||||
command: signature.command,
|
||||
args: signature.args,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import * as fspath from 'path';
|
||||
import { IConnectionItem, ILaunchSigature, panels, updateInstalledConnectionConfig, updateWorkspaceConnectionConfig } from '../global';
|
||||
import { McpOptions, panels, updateInstalledConnectionConfig, updateWorkspaceConnectionConfig } from '../global';
|
||||
import { routeMessage } from '../../openmcp-sdk/service';
|
||||
|
||||
export function getWebviewContent(context: vscode.ExtensionContext, panel: vscode.WebviewPanel): string | undefined {
|
||||
@ -35,12 +35,7 @@ export function revealOpenMcpWebviewPanel(
|
||||
context: vscode.ExtensionContext,
|
||||
type: 'workspace' | 'installed',
|
||||
panelKey: string,
|
||||
option: IConnectionItem = {
|
||||
type: 'STDIO',
|
||||
name: 'OpenMCP',
|
||||
command: 'mcp',
|
||||
args: ['run', 'main.py']
|
||||
}
|
||||
option: McpOptions[] | McpOptions
|
||||
) {
|
||||
if (panels.has(panelKey)) {
|
||||
const panel = panels.get(panelKey);
|
||||
@ -75,21 +70,9 @@ export function revealOpenMcpWebviewPanel(
|
||||
// 拦截消息,注入额外信息
|
||||
switch (command) {
|
||||
case 'vscode/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
|
||||
msg: option
|
||||
};
|
||||
|
||||
panel.webview.postMessage({
|
||||
@ -118,6 +101,8 @@ export function revealOpenMcpWebviewPanel(
|
||||
// 删除
|
||||
panels.delete(panelKey);
|
||||
|
||||
// TODO: 通过引用计数器关闭后端的 clientMap
|
||||
|
||||
// 退出
|
||||
panel.dispose();
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user