diff --git a/.vscodeignore b/.vscodeignore index 26b441c..b40231d 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -17,6 +17,7 @@ service/** test/** servers/** scripts/** +software/** *.sh *.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index f4702a3..936597d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log + +## [main] 0.0.6 +- 修复部分因为服务器名称特殊字符而导致的保存实效的错误 +- 插件模式下,左侧管理面板中的「MCP连接(工作区)」视图可以进行增删改查了 +- 新增「MCP连接(全局)」,用于安装全局范围的 mcp server + + ## [main] 0.0.5 - 支持对已经打开过的文件项目进行管理 - 支持对用户对应服务器的调试工作内容进行保存 diff --git a/icons/openmcp.png b/icons/openmcp.png index 4a77fd6..43d8b1b 100644 Binary files a/icons/openmcp.png and b/icons/openmcp.png differ diff --git a/package-lock.json b/package-lock.json index 2817125..0858cd0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.5", "dependencies": { "@modelcontextprotocol/sdk": "^1.10.2", + "@seald-io/nedb": "^4.1.1", "axios": "^1.7.7", "bson": "^6.8.0", "openai": "^4.93.0", @@ -119,6 +120,22 @@ "node": ">=18" } }, + "node_modules/@seald-io/binary-search-tree": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@seald-io/binary-search-tree/-/binary-search-tree-1.0.3.tgz", + "integrity": "sha512-qv3jnwoakeax2razYaMsGI/luWdliBLHTdC6jU55hQt1hcFqzauH/HsBollQ7IR4ySTtYhT+xyHoijpA16C+tA==" + }, + "node_modules/@seald-io/nedb": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@seald-io/nedb/-/nedb-4.1.1.tgz", + "integrity": "sha512-u7fVfzKQ/3ZaIOnYQONf2lPZtGUeQtMPjfcaQkCw/GZv5dzn20qKW6sfN0NkVbr0ksJMlWcFXNGcXYsQSb1a1g==", + "license": "MIT", + "dependencies": { + "@seald-io/binary-search-tree": "^1.0.3", + "localforage": "^1.9.0", + "util": "^0.12.4" + } + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-9.6.1.tgz", @@ -526,6 +543,21 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/axios": { "version": "1.8.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", @@ -631,6 +663,24 @@ "node": ">= 0.8" } }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -844,6 +894,23 @@ } } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1270,6 +1337,21 @@ } } }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/form-data": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", @@ -1401,6 +1483,18 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -1483,6 +1577,12 @@ "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==", "license": "Apache-2.0" }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmmirror.com/import-local/-/import-local-3.2.0.tgz", @@ -1526,6 +1626,34 @@ "node": ">= 0.10" } }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", @@ -1541,6 +1669,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", @@ -1568,6 +1714,39 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-url": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", @@ -1639,6 +1818,15 @@ "node": ">=0.10.0" } }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmmirror.com/loader-runner/-/loader-runner-4.3.0.tgz", @@ -1648,6 +1836,15 @@ "node": ">=6.11.5" } }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "license": "Apache-2.0", + "dependencies": { + "lie": "3.1.1" + } + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", @@ -2009,6 +2206,15 @@ "node": ">=8" } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2180,6 +2386,23 @@ ], "license": "MIT" }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -2284,6 +2507,23 @@ "node": ">= 18" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -2681,6 +2921,19 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", @@ -2886,6 +3139,27 @@ "node": ">= 8" } }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wildcard": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-2.0.1.tgz", diff --git a/package.json b/package.json index 5cfba22..ccac5eb 100644 --- a/package.json +++ b/package.json @@ -41,10 +41,29 @@ "dark": "./icons/dark/protocol.svg" } }, + { + "command": "openmcp.sidebar.workspace-connection.deleteConnection", + "title": "删除连接", + "category": "openmcp", + "icon": "$(trash)" + }, { "command": "openmcp.sidebar.workspace-connection.refresh", "title": "刷新", - "category": "openmcp" + "category": "openmcp", + "icon": "$(refresh)" + }, + { + "command": "openmcp.sidebar.workspace-connection.addConnection", + "title": "添加连接", + "category": "openmcp", + "icon": "$(add)" + }, + { + "command": "openmcp.sidebar.workspace-connection.openConfiguration", + "title": "打开配置", + "category": "openmcp", + "icon": "$(gear)" } ], "menus": { @@ -55,6 +74,23 @@ "when": "editorLangId == python || editorLangId == javascript || editorLangId == typescript || editorLangId == java || editorLangId == csharp" } ], + "view/title": [ + { + "command": "openmcp.sidebar.workspace-connection.refresh", + "group": "navigation", + "when": "view == openmcp.sidebar-view.workspace-connection" + }, + { + "command": "openmcp.sidebar.workspace-connection.addConnection", + "group": "navigation", + "when": "view == openmcp.sidebar-view.workspace-connection" + }, + { + "command": "openmcp.sidebar.workspace-connection.openConfiguration", + "group": "navigation", + "when": "view == openmcp.sidebar-view.workspace-connection" + } + ], "view/item/context": [ { "command": "openmcp.sidebar.workspace-connection.revealWebviewPanel", @@ -63,6 +99,14 @@ "args": { "view": "${viewItem}" } + }, + { + "command": "openmcp.sidebar.workspace-connection.deleteConnection", + "group": "inline@2", + "when": "view == openmcp.sidebar-view.workspace-connection", + "args": { + "view": "${viewItem}" + } } ] }, @@ -102,6 +146,7 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.10.2", + "@seald-io/nedb": "^4.1.1", "axios": "^1.7.7", "bson": "^6.8.0", "openai": "^4.93.0", diff --git a/renderer/src/api/message-bridge.ts b/renderer/src/api/message-bridge.ts index 4180c4b..143151c 100644 --- a/renderer/src/api/message-bridge.ts +++ b/renderer/src/api/message-bridge.ts @@ -82,7 +82,7 @@ class MessageBridge { this.postMessage = (message) => { if (this.ws?.readyState === WebSocket.OPEN) { - console.log('send', { command: message.command }); + console.log('send', message); this.ws.send(JSON.stringify(message)); } }; diff --git a/service/.gitignore b/service/.gitignore index 1355e53..cf8b8e8 100644 --- a/service/.gitignore +++ b/service/.gitignore @@ -24,6 +24,6 @@ pnpm-debug.log* config.json setting.json -tabs.example-servers/puppeteer.json *.traineddata -.env \ No newline at end of file +.env +tabs.example-servers_puppeteer.json \ No newline at end of file diff --git a/service/src/hook/db.ts b/service/src/hook/db.ts index dc63da0..a233196 100644 --- a/service/src/hook/db.ts +++ b/service/src/hook/db.ts @@ -104,6 +104,11 @@ class DiskStorage { } public setSync(filename: string, data: string | Buffer, options?: fs.WriteFileOptions): void { + + if (!fs.existsSync(this.#storageHome)) { + fs.mkdirSync(this.#storageHome, { recursive: true }); + } + const filePath = path.join(this.#storageHome, filename); fs.writeFileSync(filePath, data, options); } diff --git a/service/src/panel/panel.service.ts b/service/src/panel/panel.service.ts index 482ca83..7d41096 100644 --- a/service/src/panel/panel.service.ts +++ b/service/src/panel/panel.service.ts @@ -12,7 +12,11 @@ const DEFAULT_TABS: SaveTab = { function getTabSavePath(serverInfo: IServerVersion) { const { name = 'untitle', version = '0.0.0' } = serverInfo || {}; - const tabSaveName = `tabs.${name}.json`; + + // 过滤所有不能成为路径的字符 + const escapeName = name.replace(/[\\/:*?"<>|]/g, '_'); + + const tabSaveName = `tabs.${escapeName}.json`; // 如果是 vscode 插件下,则修改为 ~/.vscode/openmcp.json if (VSCODE_WORKSPACE) { diff --git a/software/icons/icon.icns b/software/icons/icon.icns index 3efb39f..491df25 100644 Binary files a/software/icons/icon.icns and b/software/icons/icon.icns differ diff --git a/software/icons/icon.png b/software/icons/icon.png index 238854c..33d5d03 100644 Binary files a/software/icons/icon.png and b/software/icons/icon.png differ diff --git a/software/package.json b/software/package.json index 9fbc7d4..118d899 100644 --- a/software/package.json +++ b/software/package.json @@ -31,7 +31,7 @@ }, "build": { "appId": "com.electron.openmcp", - "productName": "OpenMCP", + "productName": "OpenMCP Desktop", "asar": true, "asarUnpack": "*.node", "directories": { diff --git a/software/src/main.ts b/software/src/main.ts index b1f586f..1ad9b65 100644 --- a/software/src/main.ts +++ b/software/src/main.ts @@ -77,6 +77,10 @@ function createWindow(): void { const indexPath = path.join(__dirname, '..', 'resources/renderer/index.html'); mainWindow.loadFile(indexPath); + + setTimeout(() => { + mainWindow.webContents.openDevTools(); + }, 1000); } app.whenReady().then(() => { diff --git a/software/src/util.ts b/software/src/util.ts index 909e819..21fa7d2 100644 --- a/software/src/util.ts +++ b/software/src/util.ts @@ -2,6 +2,7 @@ import { ipcMain } from 'electron'; import * as fs from 'fs'; import * as path from 'path'; +import * as os from 'os'; export class ElectronIPCLike { private webContents: Electron.WebContents; @@ -49,8 +50,17 @@ export function refreshConnectionOption(envPath: string) { return defaultOption; } +function getEnvPath() { + const homepath = os.homedir(); + const envPathDir = path.join(homepath, '.openmcp', 'desktop'); + if (!fs.existsSync(envPathDir)) { + fs.mkdirSync(envPathDir, { recursive: true }); + } + return path.join(envPathDir, '.env'); +} + export function getInitConnectionOption() { - const envPath = path.join(__dirname, '..', '.env'); + const envPath = getEnvPath(); if (!fs.existsSync(envPath)) { return refreshConnectionOption(envPath); @@ -66,7 +76,7 @@ export function getInitConnectionOption() { } export function updateConnectionOption(data: any) { - const envPath = path.join(__dirname, '..', '.env'); + const envPath = getEnvPath(); if (data.connectionType === 'STDIO') { const connectionItem = { diff --git a/src/common/entry.ts b/src/common/entry.ts new file mode 100644 index 0000000..8f3a487 --- /dev/null +++ b/src/common/entry.ts @@ -0,0 +1,22 @@ +import * as vscode from 'vscode'; +import { registerCommands, registerTreeDataProviders } from '.'; + +export const InstallModules = [ + +]; + +export function launch(context: vscode.ExtensionContext) { + + for (const [command, value] of registerCommands) { + context.subscriptions.push(vscode.commands.registerCommand(command, (...args: any[]) => { + value.handler(context, ...args); + })); + } + + for (const [providerId, value] of registerTreeDataProviders) { + context.subscriptions.push( + vscode.window.registerTreeDataProvider(providerId, value.provider) + ); + } + +} \ No newline at end of file diff --git a/src/common/index.dto.ts b/src/common/index.dto.ts new file mode 100644 index 0000000..16cebe8 --- /dev/null +++ b/src/common/index.dto.ts @@ -0,0 +1,24 @@ +import type { TreeDataProvider, ExtensionContext } from 'vscode'; + +export interface CustomDescriptor { + configurable?: boolean; + enumerable?: boolean; + value?: T; + writable?: boolean; + get?(): any; + set?(v: any): void; +} + +export interface IRegisterCommandItem { + handler: (context: ExtensionContext, ...args: any[]) => void; + options?: any; +} + +export type CommandHandlerDescriptor = CustomDescriptor; + +export interface IRegisterTreeDataProviderItem { + provider: TreeDataProvider; + options?: any; +} + +export type TreeDataProviderDescriptor = CustomDescriptor['provider']>; \ No newline at end of file diff --git a/src/common/index.ts b/src/common/index.ts new file mode 100644 index 0000000..e929af4 --- /dev/null +++ b/src/common/index.ts @@ -0,0 +1,34 @@ +import { CommandHandlerDescriptor, IRegisterCommandItem, IRegisterTreeDataProviderItem, TreeDataProviderDescriptor } from "./index.dto"; + +export const registerCommands = new Map(); +export const registerTreeDataProviders = new Map>(); + +export function RegisterCommand(command: string, options?: any) { + return function(target: any, propertyKey: string, descriptor: CommandHandlerDescriptor) { + const handler = descriptor.value; + + // 根据 option 进行的操作 + // ... + + if (handler) { + registerCommands.set(command, { handler, options }); + } + + return descriptor; + } +} + +export function RegisterTreeDataProvider(command: string, options?: any) { + return function(target: any, propertyKey: string, descriptor: TreeDataProviderDescriptor) { + const provider = descriptor.value; + + // 根据 option 进行的操作 + // ... + + if (provider) { + registerTreeDataProviders.set(command, { provider, options }); + } + + return descriptor; + } +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 9f5afbc..2d540f4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,10 +1,10 @@ import * as vscode from 'vscode'; -import * as fspath from 'path'; import * as OpenMCPService from '../resources/service'; import { getDefaultLanunchSigature, getLaunchCWD, revealOpenMcpWebviewPanel } from './webview'; -import { ConnectionViewItem, registerSidebar } from './sidebar'; -import { getWorkspaceConnectionConfigItemByPath, ISSEConnectionItem, IStdioConnectionItem } from './global'; +import { registerSidebar } from './sidebar'; +import { getWorkspaceConnectionConfigItemByPath } from './global'; +import type { ConnectionViewItem } from './sidebar/common'; export function activate(context: vscode.ExtensionContext) { console.log('activate openmcp'); @@ -21,8 +21,14 @@ export function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand('openmcp.sidebar.workspace-connection.revealWebviewPanel', (view: ConnectionViewItem) => { const item = view.item; revealOpenMcpWebviewPanel(context, item.filePath || item.name, item); - }) - ); + } + )); + + context.subscriptions.push( + vscode.commands.registerCommand('openmcp.sidebar.workspace-connection.deleteConnection', (view: ConnectionViewItem) => { + deleteConnection(context, view); + } + )); context.subscriptions.push( vscode.commands.registerCommand('openmcp.showOpenMCP', async (uri: vscode.Uri) => { diff --git a/src/sidebar.ts b/src/sidebar.ts deleted file mode 100644 index c91bb81..0000000 --- a/src/sidebar.ts +++ /dev/null @@ -1,122 +0,0 @@ -import * as vscode from 'vscode'; -import { getConnectionConfig, getWorkspaceConnectionConfig, IConnectionItem } from './global'; - -class McpWorkspaceConnectProvider implements vscode.TreeDataProvider { - private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); - readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; - - constructor(private context: vscode.ExtensionContext) { - } - - // 实现 TreeDataProvider 接口 - getTreeItem(element: ConnectionViewItem): vscode.TreeItem { - return element; - } - - getChildren(element?: ConnectionViewItem): Thenable { - // TODO: 读取 configDir 下的所有文件,作为子节点 - const connection = getWorkspaceConnectionConfig(); - const sidebarItems = connection.items.map((item, index) => { - // 连接的名字 - const itemName = `${item.name} (${item.type})` - return new ConnectionViewItem(itemName, vscode.TreeItemCollapsibleState.None, item, 'server'); - }) - - // 返回子节点 - return Promise.resolve(sidebarItems); - } - - // 添加 refresh 方法 - public refresh(): void { - this._onDidChangeTreeData.fire(); - } -} - -// 在 registerSidebar 函数中注册 refresh 命令 -export function registerSidebar(context: vscode.ExtensionContext) { - const workspaceConnectionProvider = new McpWorkspaceConnectProvider(context); - - // 注册 refresh 命令 - context.subscriptions.push( - vscode.commands.registerCommand('openmcp.sidebar.workspace-connection.refresh', () => { - workspaceConnectionProvider.refresh(); - }) - ); - - // 注册 MCP 连接的 sidebar 视图 - context.subscriptions.push( - vscode.window.registerTreeDataProvider('openmcp.sidebar-view.workspace-connection', workspaceConnectionProvider) - ); - - // 注册 入门与帮助的 sidebar 视图 - context.subscriptions.push( - vscode.window.registerTreeDataProvider('openmcp.sidebar.help', new HelpProvider(context)) - ); -} - -class HelpProvider implements vscode.TreeDataProvider { - - constructor(private context: vscode.ExtensionContext) { - } - - // 实现 TreeDataProvider 接口 - getTreeItem(element: SidebarItem): vscode.TreeItem { - return element; - } - - getChildren(element?: SidebarItem): Thenable { - // 返回子节点 - return Promise.resolve([ - new SidebarItem('入门', vscode.TreeItemCollapsibleState.None, { - command: 'vscode.open', - title: 'Open Guide', - arguments: [vscode.Uri.parse('https://zhuanlan.zhihu.com/p/1894785817186121106')] - }, 'book'), - new SidebarItem('阅读文档', vscode.TreeItemCollapsibleState.None, { - command: 'vscode.open', - title: 'Open Documentation', - arguments: [vscode.Uri.parse('https://document.kirigaya.cn/blogs/openmcp/main.html')] - }, 'file-text'), - new SidebarItem('报告问题', vscode.TreeItemCollapsibleState.None, { - command: 'vscode.open', - title: 'Report Issue', - arguments: [vscode.Uri.parse('https://github.com/LSTM-Kirigaya/openmcp-client/issues')] - }, 'bug'), - new SidebarItem('参与项目', vscode.TreeItemCollapsibleState.None, { - command: 'vscode.open', - title: 'Join Project', - arguments: [vscode.Uri.parse('https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD')] - }, 'organization'), - new SidebarItem('评论插件', vscode.TreeItemCollapsibleState.None, { - command: 'vscode.open', - title: 'Review Extension', - arguments: [vscode.Uri.parse('https://marketplace.visualstudio.com/items?itemName=kirigaya.openmcp&ssr=false#review-details')] - }, 'feedback') - ]); - } -} - -class SidebarItem extends vscode.TreeItem { - constructor( - public readonly label: string, - public readonly collapsibleState: vscode.TreeItemCollapsibleState, - public readonly command?: vscode.Command, - public readonly icon?: string - ) { - super(label, collapsibleState); - this.command = command; - this.iconPath = new vscode.ThemeIcon(icon || 'circle-outline'); - } -} - -export class ConnectionViewItem extends vscode.TreeItem { - constructor( - public readonly label: string, - public readonly collapsibleState: vscode.TreeItemCollapsibleState, - public readonly item: IConnectionItem, - public readonly icon?: string - ) { - super(label, collapsibleState); - this.iconPath = new vscode.ThemeIcon(icon || 'circle-outline'); - } -} \ No newline at end of file diff --git a/src/sidebar/common.ts b/src/sidebar/common.ts new file mode 100644 index 0000000..f078258 --- /dev/null +++ b/src/sidebar/common.ts @@ -0,0 +1,27 @@ +import * as vscode from 'vscode'; +import type { IConnectionItem } from '../global'; + +export class SidebarItem extends vscode.TreeItem { + constructor( + public readonly label: string, + public readonly collapsibleState: vscode.TreeItemCollapsibleState, + public readonly command?: vscode.Command, + public readonly icon?: string + ) { + super(label, collapsibleState); + this.command = command; + this.iconPath = new vscode.ThemeIcon(icon || 'circle-outline'); + } +} + +export class ConnectionViewItem extends vscode.TreeItem { + constructor( + public readonly label: string, + public readonly collapsibleState: vscode.TreeItemCollapsibleState, + public readonly item: IConnectionItem, + public readonly icon?: string + ) { + super(label, collapsibleState); + this.iconPath = new vscode.ThemeIcon(icon || 'circle-outline'); + } +} \ No newline at end of file diff --git a/src/sidebar/global@2.ts b/src/sidebar/global@2.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/sidebar/help@3.ts b/src/sidebar/help@3.ts new file mode 100644 index 0000000..dcdaf0f --- /dev/null +++ b/src/sidebar/help@3.ts @@ -0,0 +1,45 @@ +import * as vscode from 'vscode'; +import { SidebarItem } from './common'; + +export class HelpProvider implements vscode.TreeDataProvider { + + constructor(private context: vscode.ExtensionContext) { + } + + // 实现 TreeDataProvider 接口 + getTreeItem(element: SidebarItem): vscode.TreeItem { + return element; + } + + getChildren(element?: SidebarItem): Thenable { + // 返回子节点 + return Promise.resolve([ + new SidebarItem('入门', vscode.TreeItemCollapsibleState.None, { + command: 'vscode.open', + title: 'Open Guide', + arguments: [vscode.Uri.parse('https://zhuanlan.zhihu.com/p/1896301240826184013')] + }, 'book'), + new SidebarItem('阅读文档', vscode.TreeItemCollapsibleState.None, { + command: 'vscode.open', + title: 'Open Documentation', + arguments: [vscode.Uri.parse('https://document.kirigaya.cn/blogs/openmcp/main.html')] + }, 'file-text'), + new SidebarItem('报告问题', vscode.TreeItemCollapsibleState.None, { + command: 'vscode.open', + title: 'Report Issue', + arguments: [vscode.Uri.parse('https://github.com/LSTM-Kirigaya/openmcp-client/issues')] + }, 'bug'), + new SidebarItem('参与项目', vscode.TreeItemCollapsibleState.None, { + command: 'vscode.open', + title: 'Join Project', + arguments: [vscode.Uri.parse('https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD')] + }, 'organization'), + new SidebarItem('评论插件', vscode.TreeItemCollapsibleState.None, { + command: 'vscode.open', + title: 'Review Extension', + arguments: [vscode.Uri.parse('https://marketplace.visualstudio.com/items?itemName=kirigaya.openmcp&ssr=false#review-details')] + }, 'feedback') + ]); + } +} + diff --git a/src/sidebar/index.ts b/src/sidebar/index.ts new file mode 100644 index 0000000..0e7c595 --- /dev/null +++ b/src/sidebar/index.ts @@ -0,0 +1,29 @@ +import * as vscode from 'vscode'; +import { McpWorkspaceConnectProvider } from './workspace@1'; +import { HelpProvider } from './help@3'; + +// 在 registerSidebar 函数中注册 refresh 命令 +export function registerSidebar(context: vscode.ExtensionContext) { + const workspaceConnectionProvider = new McpWorkspaceConnectProvider(context); + + // 注册 refresh 命令 + context.subscriptions.push( + vscode.commands.registerCommand('openmcp.sidebar.workspace-connection.refresh', () => { + workspaceConnectionProvider.refresh(); + }) + ); + + // 注册 MCP 连接的 sidebar 视图 + context.subscriptions.push( + vscode.window.registerTreeDataProvider('openmcp.sidebar-view.workspace-connection', workspaceConnectionProvider) + ); + + // 注册 MCP 连接的 sidebar 视图 + + + // 注册 入门与帮助的 sidebar 视图 + context.subscriptions.push( + vscode.window.registerTreeDataProvider('openmcp.sidebar.help', new HelpProvider(context)) + ); +} + diff --git a/src/sidebar/workspace@1.ts b/src/sidebar/workspace@1.ts new file mode 100644 index 0000000..65fd3e7 --- /dev/null +++ b/src/sidebar/workspace@1.ts @@ -0,0 +1,69 @@ +import * as vscode from 'vscode'; +import { ConnectionViewItem } from './common'; +import { getWorkspaceConnectionConfig, getWorkspacePath, panels, saveWorkspaceConnectionConfig } from '../global'; + +export class McpWorkspaceConnectProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + + constructor(private context: vscode.ExtensionContext) { + } + + // 实现 TreeDataProvider 接口 + getTreeItem(element: ConnectionViewItem): vscode.TreeItem { + return element; + } + + getChildren(element?: ConnectionViewItem): Thenable { + // TODO: 读取 configDir 下的所有文件,作为子节点 + const connection = getWorkspaceConnectionConfig(); + const sidebarItems = connection.items.map((item, index) => { + // 连接的名字 + const itemName = `${item.name} (${item.type})` + return new ConnectionViewItem(itemName, vscode.TreeItemCollapsibleState.None, item, 'server'); + }) + + // 返回子节点 + return Promise.resolve(sidebarItems); + } + + // 添加 refresh 方法 + public refresh(): void { + this._onDidChangeTreeData.fire(); + } +} + +export async function deleteConnection(context: vscode.ExtensionContext, view: ConnectionViewItem) { + const workspaceConnectionConfig = getWorkspaceConnectionConfig(); + const connectionItem = view.item; + + // 弹出确认对话框 + const confirm = await vscode.window.showWarningMessage( + `确定要删除连接 "${connectionItem.name}" 吗?`, + { modal: true }, + '确定' + ); + + if (confirm !== '确定') { + return; // 用户取消删除 + } + + // 从配置中移除该连接项 + const index = workspaceConnectionConfig.items.indexOf(connectionItem); + if (index !== -1) { + workspaceConnectionConfig.items.splice(index, 1); + + // 保存更新后的配置 + const workspacePath = getWorkspacePath(); + saveWorkspaceConnectionConfig(workspacePath); + + // 刷新侧边栏视图 + vscode.commands.executeCommand('openmcp.sidebar.workspace-connection.refresh'); + + // 如果该连接有对应的webview面板,则关闭它 + if (panels.has(connectionItem.filePath || connectionItem.name)) { + const panel = panels.get(connectionItem.filePath || connectionItem.name); + panel?.dispose(); + } + } +} \ No newline at end of file diff --git a/src/webview.ts b/src/webview.ts index 3ed7e7b..e5806e7 100644 --- a/src/webview.ts +++ b/src/webview.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import * as fs from 'fs'; import * as fspath from 'path'; -import { IConnectionItem, ILaunchSigature, panels, updateWorkspaceConnectionConfig } from './global'; +import { getWorkspaceConnectionConfig, getWorkspacePath, IConnectionItem, ILaunchSigature, panels, saveWorkspaceConnectionConfig, updateWorkspaceConnectionConfig } from './global'; import * as OpenMCPService from '../resources/service'; @@ -128,4 +128,4 @@ export function getDefaultLanunchSigature(path: string, cwd: string) { args: [relativePath] }; } -} \ No newline at end of file +}