diff --git a/renderer/src/hook/type.ts b/renderer/src/hook/type.ts index 3246641..d0af559 100644 --- a/renderer/src/hook/type.ts +++ b/renderer/src/hook/type.ts @@ -133,4 +133,39 @@ export type APIRequest = | BaseRequest | ResourcesReadRequest | PromptsGetRequest - | ToolCallRequest; \ No newline at end of file + | 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; diff --git a/renderer/src/views/connect/connection.ts b/renderer/src/views/connect/connection.ts index 64c28fe..807b9d9 100644 --- a/renderer/src/views/connect/connection.ts +++ b/renderer/src/views/connect/connection.ts @@ -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,17 +139,38 @@ 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 (updateCommandString) { - connectionArgs.commandString = commandString; - if (connectionArgs.commandString.length === 0) { - return; + if (connectionItem.type === 'stdio') { + if (updateCommandString) { + 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((resolve, reject) => { // 监听 connect @@ -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((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((resolve, reject) => { + +function getLaunchSignature() { + return new Promise((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: {} }); }) diff --git a/service/src/controller/connect.ts b/service/src/controller/connect.ts index 1e984a7..d23b3ae 100644 --- a/service/src/controller/connect.ts +++ b/service/src/controller/connect.ts @@ -3,10 +3,9 @@ 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'; +type ConnectionType = 'STDIO' | 'SSE'; type McpTransport = StdioClientTransport | SSEClientTransport; export type IServerVersion = Implementation | undefined; diff --git a/service/src/controller/index.ts b/service/src/controller/index.ts index ca2fc16..7066c1c 100644 --- a/service/src/controller/index.ts +++ b/service/src/controller/index.ts @@ -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 { diff --git a/service/src/index.ts b/service/src/index.ts index 3c29ddc..3477dca 100644 --- a/service/src/index.ts +++ b/service/src/index.ts @@ -1,3 +1,5 @@ export { messageController } from './controller'; export { VSCodeWebViewLike } from './adapter'; -export { setVscodeWorkspace } from './util'; \ No newline at end of file +export { setVscodeWorkspace } from './util'; +// TODO: 更加规范 +export { client } from './controller'; \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index facdc43..a4e7088 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -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); }) ); diff --git a/src/global.ts b/src/global.ts index 42507ef..3fa57ab 100644 --- a/src/global.ts +++ b/src/global.ts @@ -10,6 +10,7 @@ export const panels = new Map(); 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,14 +156,21 @@ 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) { - const connectionItem = getWorkspaceConnectionConfigItemByPath(absPath); +interface ServerInfo { + name: string; + version: string; +} + +export function updateWorkspaceConnectionConfig( + absPath: string, + data: (ClientStdioConnectionItem | ClientSseConnectionItem) & { serverInfo: ServerInfo } +) { + const connectionItem = getWorkspaceConnectionConfigItemByPath(absPath); const workspaceConnectionConfig = getWorkspaceConnectionConfig(); // 如果存在,删除老的 connectionItem @@ -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, '/'); diff --git a/src/sidebar.ts b/src/sidebar.ts index cfa3c81..c91bb81 100644 --- a/src/sidebar.ts +++ b/src/sidebar.ts @@ -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 { private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); @@ -18,7 +18,7 @@ class McpWorkspaceConnectProvider implements vscode.TreeDataProvider { // 连接的名字 - 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