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