Compare commits
8 Commits
a1bcd06acd
...
23d0ce1d93
Author | SHA1 | Date | |
---|---|---|---|
23d0ce1d93 | |||
9b45c272b4 | |||
c1cad24790 | |||
e9e9d7e119 | |||
![]() |
e72b03a995 | ||
![]() |
7c30463062 | ||
![]() |
72248864da | ||
![]() |
86cd49fae6 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,3 +14,4 @@ resources/ocr/*.wasm
|
|||||||
resources/renderer
|
resources/renderer
|
||||||
resources/service
|
resources/service
|
||||||
*.traineddata
|
*.traineddata
|
||||||
|
.turbo
|
||||||
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -12,5 +12,8 @@
|
|||||||
"typescript.tsc.autoDetect": "off",
|
"typescript.tsc.autoDetect": "off",
|
||||||
|
|
||||||
"i18n-haru.root": "renderer/src/i18n",
|
"i18n-haru.root": "renderer/src/i18n",
|
||||||
"i18n-haru.main": "zh"
|
"i18n-haru.main": "zh",
|
||||||
|
"i18n-ally.localesPaths": [
|
||||||
|
"renderer/src/i18n"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
28
.vscode/tabs.example-servers_puppeteer.json
vendored
28
.vscode/tabs.example-servers_puppeteer.json
vendored
@ -331,6 +331,34 @@
|
|||||||
"state": "success",
|
"state": "success",
|
||||||
"serverName": "Huoshan DeepSeek"
|
"serverName": "Huoshan DeepSeek"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "请将下面的更新日志翻译成 GitHub release 风格的英文说明,请只返回翻译后的结果,不要出现任何多余的前缀: ",
|
||||||
|
"extraInfo": {
|
||||||
|
"created": 1747598248965,
|
||||||
|
"state": "success",
|
||||||
|
"serverName": "deepseek"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "Here’s the translation in GitHub release style:\n\n---\n\n### **New Features**\n- Added support for real-time data synchronization.\n- Introduced a new dashboard for analytics.\n\n### **Improvements**\n- Optimized query performance for large datasets.\n- Enhanced error handling for API requests.\n\n### **Bug Fixes**\n- Fixed an issue where the system crashed on invalid input.\n- Resolved a memory leak in the background service.\n\n### **Breaking Changes**\n- Removed deprecated endpoints (`/v1/old`).\n- Updated the authentication mechanism to use OAuth 2.0.\n\n### **Documentation**\n- Added detailed guides for API integration.\n- Improved inline code comments.",
|
||||||
|
"extraInfo": {
|
||||||
|
"created": 1747598260799,
|
||||||
|
"state": "success",
|
||||||
|
"serverName": "deepseek",
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 7946,
|
||||||
|
"completion_tokens": 136,
|
||||||
|
"total_tokens": 8082,
|
||||||
|
"prompt_tokens_details": {
|
||||||
|
"cached_tokens": 896
|
||||||
|
},
|
||||||
|
"prompt_cache_hit_tokens": 896,
|
||||||
|
"prompt_cache_miss_tokens": 7050
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
## [main] 0.0.9
|
## [main] 0.0.9
|
||||||
- 修复 0.0.8 引入的bug:system prompt 返回的是索引而非真实内容
|
- 修复 0.0.8 引入的 bug:system prompt 返回的是索引而非真实内容
|
||||||
-
|
- 新特性:支持同时连入多个 mcp server
|
||||||
|
- 新特性:更新协议内容,支持 streamable http 协议,未来将逐步取代 SSE 的连接方式
|
||||||
|
|
||||||
## [main] 0.0.8
|
## [main] 0.0.8
|
||||||
- 大模型 API 测试时更加完整的报错
|
- 大模型 API 测试时更加完整的报错
|
||||||
|
11
README.md
11
README.md
@ -145,10 +145,7 @@ B <--mcp--> m(MCP Server)
|
|||||||
启动 dev server
|
启动 dev server
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
## linux
|
npm run dev
|
||||||
./dev.sh
|
|
||||||
## windows
|
|
||||||
./dev.ps1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
> 端口占用: 8282 (renderer) + 8081 (service)
|
> 端口占用: 8282 (renderer) + 8081 (service)
|
||||||
@ -163,11 +160,7 @@ B <--mcp--> m(MCP Server)
|
|||||||
|
|
||||||
负载部署
|
负载部署
|
||||||
```bash
|
```bash
|
||||||
## linux
|
npm run build
|
||||||
./build_service.sh
|
|
||||||
|
|
||||||
## windows
|
|
||||||
./build_service.ps1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
and just press f5, いただきます
|
and just press f5, いただきます
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
# 安装 renderer 依赖
|
## 安装 renderer 依赖
|
||||||
Set-Location renderer
|
#Set-Location renderer
|
||||||
npm i
|
#npm i
|
||||||
Set-Location ..
|
#Set-Location ..
|
||||||
|
#
|
||||||
# 安装 service 依赖并打补丁
|
## 安装 service 依赖并打补丁
|
||||||
Set-Location service
|
#Set-Location service
|
||||||
npm i
|
#npm i
|
||||||
node patch-mcp-sdk.js
|
#node patch-mcp-sdk.js
|
||||||
Set-Location ..
|
#Set-Location ..
|
||||||
|
|
||||||
Set-Location servers
|
Set-Location servers
|
||||||
uv sync
|
uv sync
|
||||||
@ -15,3 +15,5 @@ Set-Location ..
|
|||||||
|
|
||||||
# 安装根目录依赖
|
# 安装根目录依赖
|
||||||
npm i
|
npm i
|
||||||
|
|
||||||
|
npm run prepare:ocr
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
cd renderer && npm i && cd ..
|
cd servers && uv sync && cd ..
|
||||||
cd service && npm i && cd ..
|
|
||||||
cd servers && uv sync
|
|
||||||
npm i
|
npm i
|
||||||
npm run prepare:ocr
|
npm run prepare:ocr
|
15563
package-lock.json
generated
15563
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@ -2,7 +2,7 @@
|
|||||||
"name": "openmcp",
|
"name": "openmcp",
|
||||||
"displayName": "OpenMCP",
|
"displayName": "OpenMCP",
|
||||||
"description": "An all in one MCP Client/TestTool",
|
"description": "An all in one MCP Client/TestTool",
|
||||||
"version": "0.0.8",
|
"version": "0.1.0",
|
||||||
"publisher": "kirigaya",
|
"publisher": "kirigaya",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "kirigaya",
|
"name": "kirigaya",
|
||||||
@ -212,7 +212,18 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"workspaces": [
|
||||||
|
"service",
|
||||||
|
"renderer",
|
||||||
|
"software"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"dev": "turbo dev --filter=!@openmcp/electron",
|
||||||
|
"dev:electron": "turbo dev --filter=@openmcp/electron",
|
||||||
|
"dev:all": "turbo dev",
|
||||||
|
"build": "turbo build --filter=!@openmcp/electron",
|
||||||
|
"build:electron": "turbo build --filter=@openmcp/electron",
|
||||||
|
"build:all": "turbo build",
|
||||||
"vscode:prepublish": "webpack --mode production",
|
"vscode:prepublish": "webpack --mode production",
|
||||||
"compile": "tsc -p ./",
|
"compile": "tsc -p ./",
|
||||||
"watch": "tsc -watch -p ./",
|
"watch": "tsc -watch -p ./",
|
||||||
@ -224,6 +235,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.10.2",
|
"@modelcontextprotocol/sdk": "^1.10.2",
|
||||||
|
"@openmcp/service": "*",
|
||||||
"@seald-io/nedb": "^4.1.1",
|
"@seald-io/nedb": "^4.1.1",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"bson": "^6.8.0",
|
"bson": "^6.8.0",
|
||||||
@ -242,8 +254,10 @@
|
|||||||
"fork-ts-checker-webpack-plugin": "^9.1.0",
|
"fork-ts-checker-webpack-plugin": "^9.1.0",
|
||||||
"null-loader": "^4.0.1",
|
"null-loader": "^4.0.1",
|
||||||
"ts-loader": "^9.5.1",
|
"ts-loader": "^9.5.1",
|
||||||
|
"turbo": "^2.5.3",
|
||||||
"typescript": "^5.4.2",
|
"typescript": "^5.4.2",
|
||||||
"webpack": "^5.99.5",
|
"webpack": "^5.99.5",
|
||||||
"webpack-cli": "^5.1.4"
|
"webpack-cli": "^5.1.4"
|
||||||
}
|
},
|
||||||
|
"packageManager": "npm@10.0.0"
|
||||||
}
|
}
|
||||||
|
@ -1 +1,2 @@
|
|||||||
|
VITE_USE_AUTH=false
|
||||||
VITE_WEBSOCKET_URL=ws://localhost:8282
|
VITE_WEBSOCKET_URL=ws://localhost:8282
|
@ -1 +1,2 @@
|
|||||||
|
VITE_USE_AUTH=false
|
||||||
VITE_WEBSOCKET_URL=ws://localhost:8282
|
VITE_WEBSOCKET_URL=ws://localhost:8282
|
@ -1,33 +1,18 @@
|
|||||||
# test-vite
|
## dev
|
||||||
|
|
||||||
This template should help get you started developing with Vue 3 in Vite.
|
如果想要部署到公网中,想要通过密码认证才能进入,进行如下步骤:
|
||||||
|
|
||||||
## Recommended IDE Setup
|
```bash
|
||||||
|
touch .env.website.local
|
||||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
|
||||||
|
|
||||||
## Type Support for `.vue` Imports in TS
|
|
||||||
|
|
||||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
|
||||||
|
|
||||||
## Customize configuration
|
|
||||||
|
|
||||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
|
||||||
|
|
||||||
## Project Setup
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compile and Hot-Reload for Development
|
写入:
|
||||||
|
|
||||||
```sh
|
```toml
|
||||||
npm run dev
|
VITE_USE_AUTH=true
|
||||||
|
VITE_WEBSOCKET_URL=wss://<IP>/<路径>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Type-Check, Compile and Minify for Production
|
使用 `npm run serve:website` 进行测试(服务端使用 ts-node src/server.ts)
|
||||||
|
|
||||||
```sh
|
使用 `npm run build:website` 进行打包
|
||||||
npm run build
|
|
||||||
```
|
|
@ -1,9 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "test-vite",
|
"name": "@openmcp/renderer",
|
||||||
"version": "0.0.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"postbuild": "node scripts/post-build.mjs",
|
||||||
|
"lint": "vue-cli-service lint",
|
||||||
"serve": "vite",
|
"serve": "vite",
|
||||||
"serve:website": "vite --mode website",
|
"serve:website": "vite --mode website",
|
||||||
"build": "run-p type-check \"build-only {@}\" --",
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
|
BIN
renderer/public/CascadiaCode.woff2
Normal file
BIN
renderer/public/CascadiaCode.woff2
Normal file
Binary file not shown.
43
renderer/scripts/post-build.mjs
Normal file
43
renderer/scripts/post-build.mjs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import fsPath from 'node:path';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import * as process from "node:process";
|
||||||
|
|
||||||
|
function createDirIfExists(filePath) {
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
fs.mkdirSync(filePath, { recursive: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function recreateDir(filePath) {
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
fs.rmSync(filePath, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.mkdirSync(filePath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentDir = process.cwd();
|
||||||
|
// 确保上级目录的 openmcp-sdk 存在
|
||||||
|
const openMCPSdkPath = fsPath.join(currentDir, '..', 'openmcp-sdk')
|
||||||
|
createDirIfExists(openMCPSdkPath);
|
||||||
|
|
||||||
|
const sdkRenderPath = fsPath.join(openMCPSdkPath, 'renderer');
|
||||||
|
recreateDir(sdkRenderPath);
|
||||||
|
|
||||||
|
const sourceDist = fsPath.join(currentDir, 'dist');
|
||||||
|
|
||||||
|
// 如果源目录不存在则报错
|
||||||
|
if (!fs.existsSync(sourceDist)) {
|
||||||
|
throw new Error(`Source directory not found: ${sourceDist}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.cpSync(sourceDist, sdkRenderPath, { recursive: true })
|
||||||
|
|
||||||
|
// electron目录
|
||||||
|
const electronOpenMcpSdkPath = fsPath.join(currentDir, '..', 'software', 'openmcp-sdk');
|
||||||
|
createDirIfExists(electronOpenMcpSdkPath);
|
||||||
|
|
||||||
|
const electronRendererPath = fsPath.join(electronOpenMcpSdkPath, 'renderer');
|
||||||
|
recreateDir(electronRendererPath);
|
||||||
|
|
||||||
|
fs.cpSync(sourceDist, electronRendererPath, { recursive: true })
|
@ -4,7 +4,7 @@
|
|||||||
<MainPanel></MainPanel>
|
<MainPanel></MainPanel>
|
||||||
|
|
||||||
<Tour v-if="!userHasReadGuide"/>
|
<Tour v-if="!userHasReadGuide"/>
|
||||||
<PasswordDialog v-if="password"/>
|
<PasswordDialog v-if="useAuth"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -37,8 +37,8 @@ bridge.addCommandListener('hello', data => {
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const password = Boolean(import.meta.env.VITE_USE_PASSWORD);
|
const useAuth = Boolean(import.meta.env.VITE_USE_AUTH);
|
||||||
privilegeStatus.allow = !Boolean(password);
|
privilegeStatus.allow = !Boolean(useAuth);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 初始化 css
|
// 初始化 css
|
||||||
|
@ -7,9 +7,9 @@ export interface VSCodeMessage {
|
|||||||
callbackId?: string;
|
callbackId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RestFulResponse {
|
export interface RestFulResponse<T = any> {
|
||||||
code: number;
|
code: number;
|
||||||
msg: any;
|
msg: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageHandler = (message: VSCodeMessage) => void;
|
export type MessageHandler = (message: VSCodeMessage) => void;
|
||||||
@ -19,6 +19,11 @@ interface AddCommandListenerOption {
|
|||||||
once: boolean // 只调用一次就销毁
|
once: boolean // 只调用一次就销毁
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ICommandRequestData {
|
||||||
|
clientId?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
export class MessageBridge {
|
export class MessageBridge {
|
||||||
private ws: WebSocket | null = null;
|
private ws: WebSocket | null = null;
|
||||||
private handlers = new Map<string, Set<CommandHandler>>();
|
private handlers = new Map<string, Set<CommandHandler>>();
|
||||||
@ -206,7 +211,7 @@ export class MessageBridge {
|
|||||||
* @param data
|
* @param data
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public commandRequest(command: string, data?: any) {
|
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);
|
||||||
|
@ -3,6 +3,7 @@ import { pinkLog } from "@/views/setting/util";
|
|||||||
import { debugModes, tabs } from "@/components/main-panel/panel";
|
import { debugModes, tabs } from "@/components/main-panel/panel";
|
||||||
import { markRaw, ref, nextTick } from "vue";
|
import { markRaw, ref, nextTick } from "vue";
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import type { McpClient } from "@/views/connect/connection-item";
|
||||||
|
|
||||||
interface SaveTabItem {
|
interface SaveTabItem {
|
||||||
name: string;
|
name: string;
|
||||||
@ -12,64 +13,55 @@ interface SaveTabItem {
|
|||||||
storage: Record<string, any>;
|
storage: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SaveTab {
|
export interface SaveTab {
|
||||||
tabs: SaveTabItem[]
|
tabs: SaveTabItem[]
|
||||||
currentIndex: number
|
currentIndex: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const panelLoaded = ref(false);
|
export const panelLoaded = ref(false);
|
||||||
|
|
||||||
export function loadPanels() {
|
export async function loadPanels(client: McpClient) {
|
||||||
|
const bridge = useMessageBridge();
|
||||||
return new Promise((resolve, reject) => {
|
const { code, msg } = await bridge.commandRequest<SaveTab>('panel/load', {
|
||||||
const bridge = useMessageBridge();
|
clientId: client.clientId
|
||||||
|
|
||||||
bridge.addCommandListener('panel/load', data => {
|
|
||||||
if (data.code !== 200) {
|
|
||||||
pinkLog('tabs 加载失败');
|
|
||||||
console.log(data.msg);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
const persistTab = data.msg as SaveTab;
|
|
||||||
|
|
||||||
pinkLog('tabs 加载成功');
|
|
||||||
|
|
||||||
if (persistTab.tabs.length === 0) {
|
|
||||||
// 空的,直接返回不需要管
|
|
||||||
panelLoaded.value = true;
|
|
||||||
resolve(void 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
tabs.activeIndex = 0;
|
|
||||||
tabs.content = [];
|
|
||||||
|
|
||||||
for (const tab of persistTab.tabs || []) {
|
|
||||||
|
|
||||||
const component = tab.componentIndex >= 0? markRaw(debugModes[tab.componentIndex]) : undefined;
|
|
||||||
|
|
||||||
tabs.content.push({
|
|
||||||
id: uuidv4(),
|
|
||||||
name: tab.name,
|
|
||||||
icon: tab.icon,
|
|
||||||
type: tab.type,
|
|
||||||
componentIndex: tab.componentIndex,
|
|
||||||
component: component,
|
|
||||||
storage: tab.storage
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
tabs.activeIndex = persistTab.currentIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
panelLoaded.value = true;
|
|
||||||
resolve(void 0);
|
|
||||||
}, { once: true });
|
|
||||||
|
|
||||||
bridge.postMessage({
|
|
||||||
command: 'panel/load'
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
if (code !== 200) {
|
||||||
|
pinkLog('tabs 加载失败');
|
||||||
|
console.log(msg);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const persistTab = msg;
|
||||||
|
|
||||||
|
pinkLog('tabs 加载成功');
|
||||||
|
|
||||||
|
if (persistTab.tabs.length === 0) {
|
||||||
|
// 空的,直接返回不需要管
|
||||||
|
panelLoaded.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tabs.activeIndex = 0;
|
||||||
|
tabs.content = [];
|
||||||
|
|
||||||
|
for (const tab of persistTab.tabs || []) {
|
||||||
|
|
||||||
|
const component = tab.componentIndex >= 0? markRaw(debugModes[tab.componentIndex]) : undefined;
|
||||||
|
|
||||||
|
tabs.content.push({
|
||||||
|
id: uuidv4(),
|
||||||
|
name: tab.name,
|
||||||
|
icon: tab.icon,
|
||||||
|
type: tab.type,
|
||||||
|
componentIndex: tab.componentIndex,
|
||||||
|
component: component,
|
||||||
|
storage: tab.storage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tabs.activeIndex = persistTab.currentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
panelLoaded.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let debounceHandler: NodeJS.Timeout;
|
let debounceHandler: NodeJS.Timeout;
|
||||||
|
@ -149,7 +149,7 @@ export type APIRequest =
|
|||||||
| ToolCallRequest;
|
| ToolCallRequest;
|
||||||
|
|
||||||
export interface IStdioConnectionItem {
|
export interface IStdioConnectionItem {
|
||||||
type: 'stdio';
|
type: 'STDIO';
|
||||||
name: string;
|
name: string;
|
||||||
command: string;
|
command: string;
|
||||||
args: string[];
|
args: string[];
|
||||||
@ -159,7 +159,7 @@ export interface IStdioConnectionItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ISSEConnectionItem {
|
export interface ISSEConnectionItem {
|
||||||
type: 'sse';
|
type: 'SSE';
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
oauth?: string;
|
oauth?: string;
|
||||||
@ -169,13 +169,13 @@ export interface ISSEConnectionItem {
|
|||||||
|
|
||||||
|
|
||||||
export interface IStdioLaunchSignature {
|
export interface IStdioLaunchSignature {
|
||||||
type: 'stdio';
|
type: 'STDIO';
|
||||||
commandString: string;
|
commandString: string;
|
||||||
cwd: string;
|
cwd: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISSELaunchSignature {
|
export interface ISSELaunchSignature {
|
||||||
type:'sse';
|
type:'SSE';
|
||||||
url: string;
|
url: string;
|
||||||
oauth: string;
|
oauth: string;
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,10 @@
|
|||||||
<span>{{ t('connect-sigature') }}</span>
|
<span>{{ t('connect-sigature') }}</span>
|
||||||
<span style="width: 310px;">
|
<span style="width: 310px;">
|
||||||
<el-form :model="connectionArgs" :rules="rules" ref="urlForm">
|
<el-form :model="connectionArgs" :rules="rules" ref="urlForm">
|
||||||
<el-form-item prop="urlString">
|
<el-form-item prop="url">
|
||||||
<div class="input-with-label">
|
<div class="input-with-label">
|
||||||
<span class="input-label">URL</span>
|
<span class="input-label">URL</span>
|
||||||
<el-input v-model="connectionArgs.urlString" placeholder="http://"></el-input>
|
<el-input v-model="connectionArgs.url" placeholder="http://"></el-input>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="oauth">
|
<el-form-item prop="oauth">
|
||||||
@ -65,7 +65,7 @@ const rules = reactive<FormRules>({
|
|||||||
oauth: [
|
oauth: [
|
||||||
{ required: false, trigger: 'blur' }
|
{ required: false, trigger: 'blur' }
|
||||||
],
|
],
|
||||||
urlString: [
|
url: [
|
||||||
{ required: true, message: 'URL不能为空', trigger: 'blur' }
|
{ required: true, message: 'URL不能为空', trigger: 'blur' }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
286
renderer/src/views/connect/connection-item.ts
Normal file
286
renderer/src/views/connect/connection-item.ts
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
import { useMessageBridge } from "@/api/message-bridge";
|
||||||
|
import { reactive, type Reactive } from "vue";
|
||||||
|
import type { IConnectionResult, ConnectionTypeOptionItem, IConnectionArgs, IConnectionEnvironment, McpOptions } from "./type";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { loadPanels, type SaveTab } from "@/hook/panel";
|
||||||
|
import { getPlatform } from "@/api/platform";
|
||||||
|
|
||||||
|
|
||||||
|
export const connectionSelectDataViewOption: ConnectionTypeOptionItem[] = [
|
||||||
|
{
|
||||||
|
value: 'STDIO',
|
||||||
|
label: 'STDIO'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'SSE',
|
||||||
|
label: 'SSE'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'STREAMABLE_HTTP',
|
||||||
|
label: 'STREAMABLE_HTTP'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export async function getLaunchSignature(platform: string): Promise<IConnectionArgs[]> {
|
||||||
|
const bridge = useMessageBridge();
|
||||||
|
const { code, msg } = await bridge.commandRequest(platform + '/launch-signature');
|
||||||
|
|
||||||
|
if (code !== 200) {
|
||||||
|
const message = msg.toString();
|
||||||
|
ElMessage.error(message);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断一下版本,新版本的 msg 应该是数组,老版本是对象
|
||||||
|
// 返回的数组的第一个为主节点,其余为从节点
|
||||||
|
if (Array.isArray(msg)) {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
return [msg];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class McpClient {
|
||||||
|
|
||||||
|
public connectionArgs: Reactive<IConnectionArgs>;
|
||||||
|
public connectionResult: Reactive<IConnectionResult>;
|
||||||
|
|
||||||
|
public presetsEnvironment: string[] = ['HOME', 'LOGNAME', 'PATH', 'SHELL', 'TERM', 'USER'];
|
||||||
|
public connectionEnvironment: Reactive<IConnectionEnvironment>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public clientVersion: string = '0.0.1',
|
||||||
|
public clientNamePrefix: string = 'openmcp.connect'
|
||||||
|
) {
|
||||||
|
// 连接入参
|
||||||
|
this.connectionArgs = reactive({
|
||||||
|
type: 'STDIO',
|
||||||
|
commandString: '',
|
||||||
|
cwd: '',
|
||||||
|
url: '',
|
||||||
|
oauth: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// 连接出参
|
||||||
|
this.connectionResult = reactive({
|
||||||
|
success: false,
|
||||||
|
status: 'disconnected',
|
||||||
|
clientId: '',
|
||||||
|
name: '',
|
||||||
|
version: '',
|
||||||
|
logString: []
|
||||||
|
});
|
||||||
|
|
||||||
|
// 环境变量
|
||||||
|
this.connectionEnvironment = reactive({
|
||||||
|
data: [],
|
||||||
|
newKey: '',
|
||||||
|
newValue: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async acquireConnectionSignature(args: IConnectionArgs) {
|
||||||
|
this.connectionArgs.type = args.type;
|
||||||
|
this.connectionArgs.commandString = args.commandString || '';
|
||||||
|
this.connectionArgs.cwd = args.cwd || '';
|
||||||
|
this.connectionArgs.url = args.url || '';
|
||||||
|
this.connectionArgs.oauth = args.oauth || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
get clientId() {
|
||||||
|
return this.connectionResult.clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return this.connectionResult.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
get version() {
|
||||||
|
return this.connectionResult.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
get status() {
|
||||||
|
return this.connectionResult.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
get connected() {
|
||||||
|
return this.connectionResult.success;
|
||||||
|
}
|
||||||
|
|
||||||
|
get env() {
|
||||||
|
const env = {} as Record<string, string>;
|
||||||
|
this.connectionEnvironment.data.forEach(item => {
|
||||||
|
env[item.key] = item.value;
|
||||||
|
});
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get commandAndArgs() {
|
||||||
|
const commandString = this.connectionArgs.commandString;
|
||||||
|
|
||||||
|
if (!commandString) {
|
||||||
|
return { command: '', args: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = commandString.split(' ');
|
||||||
|
const command = args.shift() || '';
|
||||||
|
|
||||||
|
return { command, args };
|
||||||
|
}
|
||||||
|
|
||||||
|
get connectOption() {
|
||||||
|
const { command, args } = this.commandAndArgs;
|
||||||
|
const env = this.env;
|
||||||
|
const url = this.connectionArgs.url;
|
||||||
|
const oauth = this.connectionArgs.oauth;
|
||||||
|
const connectionType = this.connectionArgs.type;
|
||||||
|
|
||||||
|
const clientName = this.clientNamePrefix + '.' + this.connectionArgs.type;
|
||||||
|
const clientVersion = this.clientVersion;
|
||||||
|
|
||||||
|
const option: McpOptions = {
|
||||||
|
connectionType,
|
||||||
|
command,
|
||||||
|
args,
|
||||||
|
url,
|
||||||
|
oauth,
|
||||||
|
clientName,
|
||||||
|
clientVersion,
|
||||||
|
env,
|
||||||
|
serverInfo: {
|
||||||
|
name: this.connectionResult.name,
|
||||||
|
version: this.connectionResult.version
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async connect(platform: string) {
|
||||||
|
const bridge = useMessageBridge();
|
||||||
|
const { code, msg } = await bridge.commandRequest<IConnectionResult>('connect', this.connectOption);
|
||||||
|
|
||||||
|
this.connectionResult.success = (code === 200);
|
||||||
|
|
||||||
|
if (code !== 200) {
|
||||||
|
const message = msg.toString();
|
||||||
|
this.connectionResult.logString.push({
|
||||||
|
type: 'error',
|
||||||
|
message
|
||||||
|
});
|
||||||
|
|
||||||
|
ElMessage.error(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connectionResult.status = msg.status;
|
||||||
|
this.connectionResult.clientId = msg.clientId;
|
||||||
|
this.connectionResult.name = msg.name;
|
||||||
|
this.connectionResult.version = msg.version;
|
||||||
|
|
||||||
|
// 同步成功的连接参数到后端,更新 vscode treeview 中的列表
|
||||||
|
const deserializeOption = JSON.parse(JSON.stringify(this.connectOption));
|
||||||
|
|
||||||
|
bridge.postMessage({
|
||||||
|
command: platform + '/update-connection-sigature',
|
||||||
|
data: deserializeOption
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 处理环境变量开关
|
||||||
|
* - 开启时,刷新预设环境变量的数值
|
||||||
|
* - 关闭时,清空预设环境变量的数值
|
||||||
|
* @param enabled
|
||||||
|
*/
|
||||||
|
public async handleEnvSwitch(enabled: boolean) {
|
||||||
|
const presetVars = this.presetsEnvironment;
|
||||||
|
if (enabled) {
|
||||||
|
const values = await this.lookupEnvVar(presetVars);
|
||||||
|
|
||||||
|
if (values) {
|
||||||
|
// 将 key values 合并进 connectionEnv.data 中
|
||||||
|
// 若已有相同的 key, 则替换 value
|
||||||
|
for (let i = 0; i < presetVars.length; i++) {
|
||||||
|
const key = presetVars[i];
|
||||||
|
const value = values[i];
|
||||||
|
const sameNameItems = this.connectionEnvironment.data.filter(item => item.key === key);
|
||||||
|
if (sameNameItems.length > 0) {
|
||||||
|
const conflictItem = sameNameItems[0];
|
||||||
|
conflictItem.value = value;
|
||||||
|
} else {
|
||||||
|
this.connectionEnvironment.data.push({
|
||||||
|
key: key, value: value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 清空 connectionEnv.data 中所有 key 为 presetVars 的项
|
||||||
|
const reserveItems = this.connectionEnvironment.data.filter(item => !presetVars.includes(item.key));
|
||||||
|
this.connectionEnvironment.data = reserveItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 查询环境变量
|
||||||
|
* @param varNames
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async lookupEnvVar(varNames: string[]) {
|
||||||
|
const bridge = useMessageBridge();
|
||||||
|
const { code, msg } = await bridge.commandRequest('lookup-env-var', { keys: varNames });
|
||||||
|
|
||||||
|
if (code === 200) {
|
||||||
|
this.connectionResult.logString.push({
|
||||||
|
type: 'info',
|
||||||
|
message: '预设环境变量同步完成'
|
||||||
|
});
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
} else {
|
||||||
|
this.connectionResult.logString.push({
|
||||||
|
type: 'error',
|
||||||
|
message: '预设环境变量同步失败: ' + msg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class McpClientAdapter {
|
||||||
|
public clients: McpClient[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public platform: string
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async launch() {
|
||||||
|
const launchSignature = await getLaunchSignature(this.platform);
|
||||||
|
|
||||||
|
for (const item of launchSignature) {
|
||||||
|
const client = new McpClient();
|
||||||
|
|
||||||
|
// 同步连接参数
|
||||||
|
await client.acquireConnectionSignature(item);
|
||||||
|
|
||||||
|
// 同步环境变量
|
||||||
|
await client.handleEnvSwitch(true);
|
||||||
|
|
||||||
|
// 连接
|
||||||
|
await client.connect(this.platform);
|
||||||
|
|
||||||
|
this.clients.push(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async loadPanels() {
|
||||||
|
const masterNode = this.clients[0];
|
||||||
|
await loadPanels(masterNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const platform = getPlatform();
|
||||||
|
export const mcpClientAdapter = new McpClientAdapter(platform);
|
@ -1,383 +0,0 @@
|
|||||||
import { useMessageBridge } from '@/api/message-bridge';
|
|
||||||
import { reactive, ref } from 'vue';
|
|
||||||
import { pinkLog } from '../setting/util';
|
|
||||||
import { ElLoading, ElMessage } from 'element-plus';
|
|
||||||
import { getPlatform, type OpenMcpSupportPlatform } from '@/api/platform';
|
|
||||||
import { getTour, loadSetting } from '@/hook/setting';
|
|
||||||
import { loadPanels } from '@/hook/panel';
|
|
||||||
|
|
||||||
export const connectionMethods = reactive({
|
|
||||||
current: 'STDIO',
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
value: 'STDIO',
|
|
||||||
label: 'STDIO'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'SSE',
|
|
||||||
label: 'SSE'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
export const connectionSettingRef = ref<any>(null);
|
|
||||||
export const connectionLogRef = ref<any>(null);
|
|
||||||
|
|
||||||
export const connectionArgs = reactive({
|
|
||||||
commandString: '',
|
|
||||||
cwd: '',
|
|
||||||
oauth: '',
|
|
||||||
urlString: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface EnvItem {
|
|
||||||
key: string
|
|
||||||
value: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IConnectionEnv {
|
|
||||||
data: EnvItem[]
|
|
||||||
newKey: string
|
|
||||||
newValue: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const connectionEnv = reactive<IConnectionEnv>({
|
|
||||||
data: [],
|
|
||||||
newKey: '',
|
|
||||||
newValue: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
export function makeEnv() {
|
|
||||||
const env = {} as Record<string, string>;
|
|
||||||
connectionEnv.data.forEach(item => {
|
|
||||||
env[item.key] = item.value;
|
|
||||||
});
|
|
||||||
return env;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 定义连接类型
|
|
||||||
type ConnectionType = 'STDIO' | 'SSE';
|
|
||||||
|
|
||||||
// 定义命令行参数接口
|
|
||||||
export interface McpOptions {
|
|
||||||
connectionType: ConnectionType;
|
|
||||||
// STDIO 特定选项
|
|
||||||
command?: string;
|
|
||||||
args?: string[];
|
|
||||||
cwd?: string;
|
|
||||||
env?: Record<string, string>;
|
|
||||||
// SSE 特定选项
|
|
||||||
url?: string;
|
|
||||||
// 通用客户端选项
|
|
||||||
clientName?: string;
|
|
||||||
clientVersion?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function doConnect(
|
|
||||||
option: {
|
|
||||||
namespace: OpenMcpSupportPlatform
|
|
||||||
updateCommandString?: boolean
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const {
|
|
||||||
// updateCommandString 为 true 代表是初始化阶段
|
|
||||||
namespace,
|
|
||||||
updateCommandString = true
|
|
||||||
} = option;
|
|
||||||
|
|
||||||
if (updateCommandString) {
|
|
||||||
pinkLog('请求启动参数');
|
|
||||||
const connectionItem = await getLaunchSignature(namespace + '/launch-signature');
|
|
||||||
|
|
||||||
if (connectionItem.type ==='stdio') {
|
|
||||||
connectionMethods.current = 'STDIO';
|
|
||||||
connectionArgs.commandString = connectionItem.commandString;
|
|
||||||
connectionArgs.cwd = connectionItem.cwd;
|
|
||||||
|
|
||||||
if (connectionArgs.commandString.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
connectionMethods.current = 'SSE';
|
|
||||||
connectionArgs.urlString = connectionItem.url || '';
|
|
||||||
|
|
||||||
if (connectionArgs.urlString.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connectionMethods.current === 'STDIO') {
|
|
||||||
await launchStdio(namespace);
|
|
||||||
} else {
|
|
||||||
await launchSSE(namespace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function launchStdio(namespace: string) {
|
|
||||||
const bridge = useMessageBridge();
|
|
||||||
const env = makeEnv();
|
|
||||||
|
|
||||||
const commandComponents = connectionArgs.commandString.split(/\s+/g);
|
|
||||||
const command = commandComponents[0];
|
|
||||||
commandComponents.shift();
|
|
||||||
|
|
||||||
const connectOption = {
|
|
||||||
connectionType: 'STDIO',
|
|
||||||
command: command,
|
|
||||||
args: commandComponents,
|
|
||||||
cwd: connectionArgs.cwd,
|
|
||||||
clientName: 'openmcp.connect.stdio',
|
|
||||||
clientVersion: '0.0.1',
|
|
||||||
env
|
|
||||||
};
|
|
||||||
|
|
||||||
const { code, msg } = await bridge.commandRequest('connect', connectOption);
|
|
||||||
|
|
||||||
connectionResult.success = (code === 200);
|
|
||||||
|
|
||||||
if (code === 200) {
|
|
||||||
connectionResult.logString.push({
|
|
||||||
type: 'info',
|
|
||||||
message: msg
|
|
||||||
});
|
|
||||||
|
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
bridge.postMessage({
|
|
||||||
command: namespace + '/update-connection-sigature',
|
|
||||||
data: JSON.parse(JSON.stringify(clientStdioConnectionItem))
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
connectionResult.logString.push({
|
|
||||||
type: 'error',
|
|
||||||
message: msg
|
|
||||||
});
|
|
||||||
|
|
||||||
ElMessage.error(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function launchSSE(namespace: string) {
|
|
||||||
const bridge = useMessageBridge();
|
|
||||||
const env = makeEnv();
|
|
||||||
|
|
||||||
const connectOption: McpOptions = {
|
|
||||||
connectionType: 'SSE',
|
|
||||||
url: connectionArgs.urlString,
|
|
||||||
clientName: 'openmcp.connect.sse',
|
|
||||||
clientVersion: '0.0.1',
|
|
||||||
env
|
|
||||||
};
|
|
||||||
|
|
||||||
const { code, msg } = await bridge.commandRequest('connect', connectOption);
|
|
||||||
|
|
||||||
connectionResult.success = (code === 200);
|
|
||||||
|
|
||||||
if (code === 200) {
|
|
||||||
connectionResult.logString.push({
|
|
||||||
type: 'info',
|
|
||||||
message: msg
|
|
||||||
});
|
|
||||||
|
|
||||||
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: env
|
|
||||||
};
|
|
||||||
|
|
||||||
bridge.postMessage({
|
|
||||||
command: namespace + '/update-connection-sigature',
|
|
||||||
data: JSON.parse(JSON.stringify(clientSseConnectionItem))
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
connectionResult.logString.push({
|
|
||||||
type: 'error',
|
|
||||||
message: msg
|
|
||||||
});
|
|
||||||
|
|
||||||
ElMessage.error(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function getLaunchSignature(signatureName: string) {
|
|
||||||
const bridge = useMessageBridge();
|
|
||||||
const { code, msg } = await bridge.commandRequest(signatureName);
|
|
||||||
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function doReconnect() {
|
|
||||||
// TODO: finish this
|
|
||||||
console.log();
|
|
||||||
}
|
|
||||||
|
|
||||||
export const connectionResult = reactive<{
|
|
||||||
success: boolean,
|
|
||||||
logString: { type: 'info' | 'error' | 'warning', message: string }[],
|
|
||||||
serverInfo: {
|
|
||||||
name: string,
|
|
||||||
version: string
|
|
||||||
}
|
|
||||||
}>({
|
|
||||||
success: false,
|
|
||||||
logString: [],
|
|
||||||
serverInfo: {
|
|
||||||
name: '',
|
|
||||||
version: ''
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export function getServerVersion() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const bridge = useMessageBridge();
|
|
||||||
bridge.addCommandListener('server/version', data => {
|
|
||||||
if (data.code === 200) {
|
|
||||||
resolve(data.msg);
|
|
||||||
} else {
|
|
||||||
reject(data.msg);
|
|
||||||
}
|
|
||||||
}, { once: true });
|
|
||||||
|
|
||||||
bridge.postMessage({
|
|
||||||
command: 'server/version',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const envVarStatus = {
|
|
||||||
launched: false
|
|
||||||
};
|
|
||||||
|
|
||||||
function lookupEnvVar(varNames: string[]) {
|
|
||||||
const bridge = useMessageBridge();
|
|
||||||
|
|
||||||
return new Promise<string[] | undefined>((resolve, reject) => {
|
|
||||||
bridge.addCommandListener('lookup-env-var', data => {
|
|
||||||
const { code, msg } = data;
|
|
||||||
|
|
||||||
if (code === 200) {
|
|
||||||
connectionResult.logString.push({
|
|
||||||
type: 'info',
|
|
||||||
message: '预设环境变量同步完成'
|
|
||||||
});
|
|
||||||
|
|
||||||
resolve(msg);
|
|
||||||
} else {
|
|
||||||
connectionResult.logString.push({
|
|
||||||
type: 'error',
|
|
||||||
message: '预设环境变量同步失败: ' + msg
|
|
||||||
});
|
|
||||||
|
|
||||||
resolve(undefined);
|
|
||||||
}
|
|
||||||
}, { once: true });
|
|
||||||
|
|
||||||
console.log(varNames);
|
|
||||||
|
|
||||||
|
|
||||||
bridge.postMessage({
|
|
||||||
command: 'lookup-env-var',
|
|
||||||
data: {
|
|
||||||
keys: varNames
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export async function handleEnvSwitch(enabled: boolean) {
|
|
||||||
const presetVars = ['HOME', 'LOGNAME', 'PATH', 'SHELL', 'TERM', 'USER'];
|
|
||||||
|
|
||||||
if (enabled) {
|
|
||||||
const values = await lookupEnvVar(presetVars);
|
|
||||||
|
|
||||||
if (values) {
|
|
||||||
// 将 key values 合并进 connectionEnv.data 中
|
|
||||||
// 若已有相同的 key, 则替换 value
|
|
||||||
for (let i = 0; i < presetVars.length; i++) {
|
|
||||||
const key = presetVars[i];
|
|
||||||
const value = values[i];
|
|
||||||
const sameNameItems = connectionEnv.data.filter(item => item.key === key);
|
|
||||||
if (sameNameItems.length > 0) {
|
|
||||||
const conflictItem = sameNameItems[0];
|
|
||||||
conflictItem.value = value;
|
|
||||||
} else {
|
|
||||||
connectionEnv.data.push({
|
|
||||||
key: key, value: value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 清空 connectionEnv.data 中所有 key 为 presetVars 的项
|
|
||||||
const reserveItems = connectionEnv.data.filter(item => !presetVars.includes(item.key));
|
|
||||||
connectionEnv.data = reserveItems;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadEnvVar() {
|
|
||||||
return await handleEnvSwitch(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function initialise() {
|
|
||||||
|
|
||||||
pinkLog('准备请求设置');
|
|
||||||
|
|
||||||
const loading = ElLoading.service({
|
|
||||||
fullscreen: true,
|
|
||||||
lock: true,
|
|
||||||
text: 'Loading',
|
|
||||||
background: 'rgba(0, 0, 0, 0.7)'
|
|
||||||
});
|
|
||||||
const platform = getPlatform();
|
|
||||||
|
|
||||||
// 加载全局设置
|
|
||||||
loadSetting();
|
|
||||||
|
|
||||||
// 设置环境变量
|
|
||||||
await loadEnvVar();
|
|
||||||
|
|
||||||
// 获取引导状态
|
|
||||||
await getTour();
|
|
||||||
|
|
||||||
// 尝试进行初始化连接
|
|
||||||
await doConnect({
|
|
||||||
namespace: platform,
|
|
||||||
updateCommandString: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// loading panels
|
|
||||||
await loadPanels();
|
|
||||||
|
|
||||||
loading.close();
|
|
||||||
}
|
|
@ -44,7 +44,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent, ref } from 'vue';
|
import { defineComponent, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { connectionEnv, type EnvItem, handleEnvSwitch } from './connection';
|
|
||||||
|
|
||||||
defineComponent({ name: 'env-var' });
|
defineComponent({ name: 'env-var' });
|
||||||
|
|
||||||
|
30
renderer/src/views/connect/index.ts
Normal file
30
renderer/src/views/connect/index.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { getTour, loadSetting } from "@/hook/setting";
|
||||||
|
import { ElLoading } from "element-plus";
|
||||||
|
import { pinkLog } from "../setting/util";
|
||||||
|
import { mcpClientAdapter } from "./connection-item";
|
||||||
|
|
||||||
|
export async function initialise() {
|
||||||
|
|
||||||
|
pinkLog('准备请求设置');
|
||||||
|
|
||||||
|
const loading = ElLoading.service({
|
||||||
|
fullscreen: true,
|
||||||
|
lock: true,
|
||||||
|
text: 'Loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载全局设置
|
||||||
|
loadSetting();
|
||||||
|
|
||||||
|
// 获取引导状态
|
||||||
|
await getTour();
|
||||||
|
|
||||||
|
// 尝试进行初始化连接
|
||||||
|
await mcpClientAdapter.launch();
|
||||||
|
|
||||||
|
// loading panels
|
||||||
|
await mcpClientAdapter.loadPanels();
|
||||||
|
|
||||||
|
loading.close();
|
||||||
|
}
|
80
renderer/src/views/connect/type.ts
Normal file
80
renderer/src/views/connect/type.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
|
||||||
|
export type ConnectionType = 'STDIO' | 'SSE' | 'STREAMABLE_HTTP';
|
||||||
|
|
||||||
|
export interface ConnectionTypeOptionItem {
|
||||||
|
value: ConnectionType;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IConnectionArgs {
|
||||||
|
type: ConnectionType;
|
||||||
|
commandString?: string;
|
||||||
|
cwd?: string;
|
||||||
|
url?: string;
|
||||||
|
oauth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IConnectionResult {
|
||||||
|
success: boolean;
|
||||||
|
status: string
|
||||||
|
clientId: string
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
logString: {
|
||||||
|
type: 'info' | 'error' | 'warning',
|
||||||
|
message: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EnvItem {
|
||||||
|
key: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IConnectionEnvironment {
|
||||||
|
data: EnvItem[]
|
||||||
|
newKey: string
|
||||||
|
newValue: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IConnectionArgs {
|
||||||
|
type: ConnectionType;
|
||||||
|
commandString?: string;
|
||||||
|
cwd?: string;
|
||||||
|
url?: string;
|
||||||
|
oauth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface ConnectionResult {
|
||||||
|
status: string
|
||||||
|
clientId: string
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
|
10
servers/uv.lock
generated
10
servers/uv.lock
generated
@ -175,10 +175,10 @@ wheels = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httpx-sse"
|
name = "httpx-SSE"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 }
|
sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-SSE-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 },
|
{ url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 },
|
||||||
]
|
]
|
||||||
@ -258,10 +258,10 @@ source = { registry = "https://pypi.org/simple" }
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "anyio" },
|
{ name = "anyio" },
|
||||||
{ name = "httpx" },
|
{ name = "httpx" },
|
||||||
{ name = "httpx-sse" },
|
{ name = "httpx-SSE" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
{ name = "pydantic-settings" },
|
{ name = "pydantic-settings" },
|
||||||
{ name = "sse-starlette" },
|
{ name = "SSE-starlette" },
|
||||||
{ name = "starlette" },
|
{ name = "starlette" },
|
||||||
{ name = "uvicorn" },
|
{ name = "uvicorn" },
|
||||||
]
|
]
|
||||||
@ -477,7 +477,7 @@ wheels = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sse-starlette"
|
name = "SSE-starlette"
|
||||||
version = "2.2.1"
|
version = "2.2.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
5023
service/package-lock.json
generated
5023
service/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "openmcp-service",
|
"name": "@openmcp/service",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
"type": "commonjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "ts-node-dev --respawn --transpile-only src/main.ts",
|
"postinstall": "npm run build",
|
||||||
|
"dev": "ts-node-dev --respawn --transpile-only src/main.ts",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"build:watch": "tsc --watch",
|
"build:watch": "tsc --watch",
|
||||||
|
"postbuild": "node ./scripts/post-build.mjs",
|
||||||
"start": "node dist/main.js",
|
"start": "node dist/main.js",
|
||||||
"start:prod": "NODE_ENV=production node dist/main.js",
|
"start:prod": "NODE_ENV=production node dist/main.js",
|
||||||
"debug": "node --inspect -r ts-node/register src/main.ts",
|
"debug": "node --inspect -r ts-node/register src/main.ts",
|
||||||
|
33
service/scripts/post-build.mjs
Normal file
33
service/scripts/post-build.mjs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import process from "node:process";
|
||||||
|
import fsPath from "node:path";
|
||||||
|
|
||||||
|
function createDirIfExists(filePath) {
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
fs.mkdirSync(filePath, { recursive: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function recreateDir(filePath) {
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
fs.rmSync(filePath, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.mkdirSync(filePath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentDir = process.cwd();
|
||||||
|
const sourceDist = fsPath.join(currentDir, 'dist');
|
||||||
|
|
||||||
|
// 如果源目录不存在则报错
|
||||||
|
if (!fs.existsSync(sourceDist)) {
|
||||||
|
throw new Error(`Source directory not found: ${sourceDist}`)
|
||||||
|
}
|
||||||
|
// electron目录
|
||||||
|
const electronOpenMcpSdkPath = fsPath.join(currentDir, '..', 'software', 'openmcp-sdk');
|
||||||
|
createDirIfExists(electronOpenMcpSdkPath);
|
||||||
|
|
||||||
|
const electronServicePath = fsPath.join(electronOpenMcpSdkPath, 'service');
|
||||||
|
recreateDir(electronServicePath);
|
||||||
|
|
||||||
|
fs.cpSync(sourceDist, electronServicePath, { recursive: true })
|
@ -3,9 +3,13 @@ import { McpClient } from "../mcp/client.service";
|
|||||||
|
|
||||||
export type RequestClientType = McpClient | undefined;
|
export type RequestClientType = McpClient | undefined;
|
||||||
|
|
||||||
|
export interface RequestData {
|
||||||
|
clientId?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
export type RequestHandler<T, R> = (
|
export type RequestHandler<T, R> = (
|
||||||
client: RequestClientType,
|
data: T & RequestData,
|
||||||
data: T,
|
|
||||||
webview: PostMessageble
|
webview: PostMessageble
|
||||||
) => Promise<R>;
|
) => Promise<R>;
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import type { PostMessageble } from "../hook/adapter";
|
|||||||
import { LlmController } from "../llm/llm.controller";
|
import { LlmController } from "../llm/llm.controller";
|
||||||
import { ClientController } from "../mcp/client.controller";
|
import { ClientController } from "../mcp/client.controller";
|
||||||
import { ConnectController } from "../mcp/connect.controller";
|
import { ConnectController } from "../mcp/connect.controller";
|
||||||
import { client } from "../mcp/connect.service";
|
|
||||||
import { OcrController } from "../mcp/ocr.controller";
|
import { OcrController } from "../mcp/ocr.controller";
|
||||||
import { PanelController } from "../panel/panel.controller";
|
import { PanelController } from "../panel/panel.controller";
|
||||||
import { SettingController } from "../setting/setting.controller";
|
import { SettingController } from "../setting/setting.controller";
|
||||||
@ -24,7 +23,7 @@ export async function routeMessage(command: string, data: any, webview: PostMess
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// TODO: select client based on something
|
// TODO: select client based on something
|
||||||
const res = await handler(client, data, webview);
|
const res = await handler(data, webview);
|
||||||
|
|
||||||
// res.code = -1 代表当前请求不需要返回发送
|
// res.code = -1 代表当前请求不需要返回发送
|
||||||
if (res.code >= 0) {
|
if (res.code >= 0) {
|
||||||
|
@ -2,7 +2,7 @@ import { WebSocket } from 'ws';
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { routeMessage } from '../common/router';
|
import { routeMessage } from '../common/router';
|
||||||
import { McpOptions } from '../mcp/client.dto';
|
import { McpOptions } from '../mcp/client.dto';
|
||||||
import { client, connectService } from '../mcp/connect.service';
|
import { clientMap, connectService } from '../mcp/connect.service';
|
||||||
|
|
||||||
// WebSocket 消息格式
|
// WebSocket 消息格式
|
||||||
export interface WebSocketMessage {
|
export interface WebSocketMessage {
|
||||||
@ -114,9 +114,12 @@ export class TaskLoopAdapter {
|
|||||||
* @param mcpOption
|
* @param mcpOption
|
||||||
*/
|
*/
|
||||||
public async connectMcpServer(mcpOption: McpOptions) {
|
public async connectMcpServer(mcpOption: McpOptions) {
|
||||||
const res = await connectService(undefined, mcpOption);
|
const res = await connectService(mcpOption);
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
console.log('✅ 成功连接 mcp 服务器: ' + res.msg);
|
console.log('✅ 成功连接 mcp 服务器: ' + res.msg);
|
||||||
|
|
||||||
|
const uuid = res.msg.uuid;
|
||||||
|
const client = clientMap.get(uuid);
|
||||||
const version = client?.getServerVersion();
|
const version = client?.getServerVersion();
|
||||||
console.log(version);
|
console.log(version);
|
||||||
} else {
|
} else {
|
||||||
@ -129,14 +132,19 @@ export class TaskLoopAdapter {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public async listTools() {
|
public async listTools() {
|
||||||
const tools = await client?.listTools();
|
const tools = [];
|
||||||
if (tools?.tools) {
|
for (const client of clientMap.values()) {
|
||||||
return tools.tools.map((tool) => {
|
const clientTools = await client?.listTools();
|
||||||
const enabledTools = { ...tool, enabled: true };
|
if (clientTools?.tools) {
|
||||||
return enabledTools;
|
const enabledTools = clientTools.tools.map((tool) => {
|
||||||
});
|
const enabledTools = {...tool, enabled: true };
|
||||||
|
return enabledTools;
|
||||||
|
});
|
||||||
|
tools.push(...enabledTools);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return [];
|
|
||||||
|
return tools;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
import { Controller, RequestClientType } from "../common";
|
import { Controller, RequestClientType } from "../common";
|
||||||
|
import { RequestData } from "../common/index.dto";
|
||||||
import { PostMessageble } from "../hook/adapter";
|
import { PostMessageble } from "../hook/adapter";
|
||||||
|
import { getClient } from "../mcp/connect.service";
|
||||||
import { abortMessageService, streamingChatCompletion } from "./llm.service";
|
import { abortMessageService, streamingChatCompletion } from "./llm.service";
|
||||||
|
|
||||||
export class LlmController {
|
export class LlmController {
|
||||||
|
|
||||||
@Controller('llm/chat/completions')
|
@Controller('llm/chat/completions')
|
||||||
async chatCompletion(client: RequestClientType, data: any, webview: PostMessageble) {
|
async chatCompletion(data: RequestData, webview: PostMessageble) {
|
||||||
let { tools = [] } = data;
|
let { tools = [] } = data;
|
||||||
|
|
||||||
|
const client = getClient(data.clientId);
|
||||||
|
|
||||||
if (tools.length > 0 && !client) {
|
if (tools.length > 0 && !client) {
|
||||||
return {
|
return {
|
||||||
code: 501,
|
code: 501,
|
||||||
@ -37,7 +41,7 @@ export class LlmController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Controller('llm/chat/completions/abort')
|
@Controller('llm/chat/completions/abort')
|
||||||
async abortChatCompletion(client: RequestClientType, data: any, webview: PostMessageble) {
|
async abortChatCompletion(data: RequestData, webview: PostMessageble) {
|
||||||
return abortMessageService(data, webview);
|
return abortMessageService(data, webview);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,13 +28,13 @@ const logger = pino({
|
|||||||
export type MessageHandler = (message: VSCodeMessage) => void;
|
export type MessageHandler = (message: VSCodeMessage) => void;
|
||||||
|
|
||||||
interface IStdioLaunchSignature {
|
interface IStdioLaunchSignature {
|
||||||
type: 'stdio';
|
type: 'STDIO';
|
||||||
commandString: string;
|
commandString: string;
|
||||||
cwd: string;
|
cwd: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISSELaunchSignature {
|
interface ISSELaunchSignature {
|
||||||
type:'sse';
|
type:'SSE';
|
||||||
url: string;
|
url: string;
|
||||||
oauth: string;
|
oauth: string;
|
||||||
}
|
}
|
||||||
@ -43,7 +43,7 @@ export type ILaunchSigature = IStdioLaunchSignature | ISSELaunchSignature;
|
|||||||
|
|
||||||
function refreshConnectionOption(envPath: string) {
|
function refreshConnectionOption(envPath: string) {
|
||||||
const defaultOption = {
|
const defaultOption = {
|
||||||
type:'stdio',
|
type:'STDIO',
|
||||||
command: 'mcp',
|
command: 'mcp',
|
||||||
args: ['run', 'main.py'],
|
args: ['run', 'main.py'],
|
||||||
cwd: '../server'
|
cwd: '../server'
|
||||||
@ -76,7 +76,7 @@ function updateConnectionOption(data: any) {
|
|||||||
|
|
||||||
if (data.connectionType === 'STDIO') {
|
if (data.connectionType === 'STDIO') {
|
||||||
const connectionItem = {
|
const connectionItem = {
|
||||||
type: 'stdio',
|
type: 'STDIO',
|
||||||
command: data.command,
|
command: data.command,
|
||||||
args: data.args,
|
args: data.args,
|
||||||
cwd: data.cwd.replace(/\\/g, '/')
|
cwd: data.cwd.replace(/\\/g, '/')
|
||||||
@ -85,7 +85,7 @@ function updateConnectionOption(data: any) {
|
|||||||
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
|
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
|
||||||
} else {
|
} else {
|
||||||
const connectionItem = {
|
const connectionItem = {
|
||||||
type: 'sse',
|
type: 'SSE',
|
||||||
url: data.url,
|
url: data.url,
|
||||||
oauth: data.oauth
|
oauth: data.oauth
|
||||||
};
|
};
|
||||||
@ -124,14 +124,14 @@ wss.on('connection', (ws: any) => {
|
|||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'web/launch-signature':
|
case 'web/launch-signature':
|
||||||
const launchResultMessage: ILaunchSigature = option.type === 'stdio' ?
|
const launchResultMessage: ILaunchSigature = option.type === 'STDIO' ?
|
||||||
{
|
{
|
||||||
type: 'stdio',
|
type: 'STDIO',
|
||||||
commandString: option.command + ' ' + option.args.join(' '),
|
commandString: option.command + ' ' + option.args.join(' '),
|
||||||
cwd: option.cwd || ''
|
cwd: option.cwd || ''
|
||||||
} :
|
} :
|
||||||
{
|
{
|
||||||
type: 'sse',
|
type: 'SSE',
|
||||||
url: option.url,
|
url: option.url,
|
||||||
oauth: option.oauth || ''
|
oauth: option.oauth || ''
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import { Controller, RequestClientType } from "../common";
|
import { Controller } from "../common";
|
||||||
|
import { RequestData } from "../common/index.dto";
|
||||||
import { PostMessageble } from "../hook/adapter";
|
import { PostMessageble } from "../hook/adapter";
|
||||||
import { postProcessMcpToolcallResponse } from "./client.service";
|
import { postProcessMcpToolcallResponse } from "./client.service";
|
||||||
|
import { getClient } from "./connect.service";
|
||||||
|
|
||||||
export class ClientController {
|
export class ClientController {
|
||||||
|
|
||||||
@Controller('server/version')
|
@Controller('server/version')
|
||||||
async getServerVersion(client: RequestClientType, data: any, webview: PostMessageble) {
|
async getServerVersion(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return {
|
return {
|
||||||
code: 501,
|
code: 501,
|
||||||
@ -21,7 +24,8 @@ export class ClientController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Controller('prompts/list')
|
@Controller('prompts/list')
|
||||||
async listPrompts(client: RequestClientType, data: any, webview: PostMessageble) {
|
async listPrompts(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
if (!client) {
|
if (!client) {
|
||||||
const connectResult = {
|
const connectResult = {
|
||||||
code: 501,
|
code: 501,
|
||||||
@ -39,7 +43,8 @@ export class ClientController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Controller('prompts/get')
|
@Controller('prompts/get')
|
||||||
async getPrompt(client: RequestClientType, option: any, webview: PostMessageble) {
|
async getPrompt(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return {
|
return {
|
||||||
code: 501,
|
code: 501,
|
||||||
@ -47,7 +52,7 @@ export class ClientController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const prompt = await client.getPrompt(option.promptId, option.args || {});
|
const prompt = await client.getPrompt(data.promptId, data.args || {});
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
msg: prompt
|
msg: prompt
|
||||||
@ -55,7 +60,8 @@ export class ClientController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Controller('resources/list')
|
@Controller('resources/list')
|
||||||
async listResources(client: RequestClientType, data: any, webview: PostMessageble) {
|
async listResources(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return {
|
return {
|
||||||
code: 501,
|
code: 501,
|
||||||
@ -71,8 +77,8 @@ export class ClientController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Controller('resources/templates/list')
|
@Controller('resources/templates/list')
|
||||||
async listResourceTemplates(client: RequestClientType, data: any, webview: PostMessageble) {
|
async listResourceTemplates(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return {
|
return {
|
||||||
code: 501,
|
code: 501,
|
||||||
@ -88,7 +94,8 @@ export class ClientController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Controller('resources/read')
|
@Controller('resources/read')
|
||||||
async readResource(client: RequestClientType, option: any, webview: PostMessageble) {
|
async readResource(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return {
|
return {
|
||||||
code: 501,
|
code: 501,
|
||||||
@ -96,7 +103,7 @@ export class ClientController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const resource = await client.readResource(option.resourceUri);
|
const resource = await client.readResource(data.resourceUri);
|
||||||
console.log(resource);
|
console.log(resource);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -106,7 +113,8 @@ export class ClientController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Controller('tools/list')
|
@Controller('tools/list')
|
||||||
async listTools(client: RequestClientType, data: any, webview: PostMessageble) {
|
async listTools(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return {
|
return {
|
||||||
code: 501,
|
code: 501,
|
||||||
@ -122,7 +130,8 @@ export class ClientController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Controller('tools/call')
|
@Controller('tools/call')
|
||||||
async callTool(client: RequestClientType, option: any, webview: PostMessageble) {
|
async callTool(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return {
|
return {
|
||||||
code: 501,
|
code: 501,
|
||||||
@ -131,18 +140,13 @@ export class ClientController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const toolResult = await client.callTool({
|
const toolResult = await client.callTool({
|
||||||
name: option.toolName,
|
name: data.toolName,
|
||||||
arguments: option.toolArgs,
|
arguments: data.toolArgs,
|
||||||
callToolOption: option.callToolOption
|
callToolOption: data.callToolOption
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log(JSON.stringify(toolResult, null, 2));
|
|
||||||
|
|
||||||
postProcessMcpToolcallResponse(toolResult, webview);
|
postProcessMcpToolcallResponse(toolResult, webview);
|
||||||
|
|
||||||
// console.log(JSON.stringify(toolResult, null, 2));
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
msg: toolResult
|
msg: toolResult
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/STDIO.js";
|
||||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/SSE.js";
|
||||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||||
import { Implementation } from "@modelcontextprotocol/sdk/types";
|
import { Implementation } from "@modelcontextprotocol/sdk/types";
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||||
|
|
||||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/STDIO.js";
|
||||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/SSE.js";
|
||||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||||
import type { McpOptions, McpTransport, IServerVersion, ToolCallResponse, ToolCallContent } from './client.dto';
|
import type { McpOptions, McpTransport, IServerVersion, ToolCallResponse, ToolCallContent } from './client.dto';
|
||||||
import { PostMessageble } from "../hook/adapter";
|
import { PostMessageble } from "../hook/adapter";
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
import { Controller, RequestClientType } from '../common';
|
import { Controller } from '../common';
|
||||||
import { PostMessageble } from '../hook/adapter';
|
import { PostMessageble } from '../hook/adapter';
|
||||||
import { connectService } from './connect.service';
|
import { RequestData } from '../common/index.dto';
|
||||||
|
import { connectService, getClient } from './connect.service';
|
||||||
|
|
||||||
export class ConnectController {
|
export class ConnectController {
|
||||||
|
|
||||||
@Controller('connect')
|
@Controller('connect')
|
||||||
async connect(client: RequestClientType, data: any, webview: PostMessageble) {
|
async connect(data: any, webview: PostMessageble) {
|
||||||
const res = await connectService(client, data);
|
const res = await connectService(data);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Controller('lookup-env-var')
|
@Controller('lookup-env-var')
|
||||||
async lookupEnvVar(client: RequestClientType, data: any, webview: PostMessageble) {
|
async lookupEnvVar(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
const { keys } = data;
|
const { keys } = data;
|
||||||
const values = keys.map((key: string) => process.env[key] || '');
|
const values = keys.map((key: string) => process.env[key] || '');
|
||||||
|
|
||||||
@ -22,7 +24,8 @@ export class ConnectController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Controller('ping')
|
@Controller('ping')
|
||||||
async ping(client: RequestClientType, data: any, webview: PostMessageble) {
|
async ping(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
if (!client) {
|
if (!client) {
|
||||||
const connectResult = {
|
const connectResult = {
|
||||||
code: 501,
|
code: 501,
|
||||||
|
@ -3,10 +3,12 @@ import { RequestClientType } from '../common';
|
|||||||
import { connect } from './client.service';
|
import { connect } from './client.service';
|
||||||
import { RestfulResponse } from '../common/index.dto';
|
import { RestfulResponse } from '../common/index.dto';
|
||||||
import { McpOptions } from './client.dto';
|
import { McpOptions } from './client.dto';
|
||||||
|
import { randomUUID } from 'node:crypto';
|
||||||
|
|
||||||
|
export const clientMap: Map<string, RequestClientType> = new Map();
|
||||||
// TODO: 更多的 client
|
export function getClient(clientId?: string): RequestClientType | undefined {
|
||||||
export let client: RequestClientType = undefined;
|
return clientMap.get(clientId || '');
|
||||||
|
}
|
||||||
|
|
||||||
export function tryGetRunCommandError(command: string, args: string[] = [], cwd?: string): string | null {
|
export function tryGetRunCommandError(command: string, args: string[] = [], cwd?: string): string | null {
|
||||||
try {
|
try {
|
||||||
@ -15,7 +17,7 @@ export function tryGetRunCommandError(command: string, args: string[] = [], cwd?
|
|||||||
|
|
||||||
const result = spawnSync(command, args, {
|
const result = spawnSync(command, args, {
|
||||||
cwd: cwd || process.cwd(),
|
cwd: cwd || process.cwd(),
|
||||||
stdio: 'pipe',
|
STDIO: 'pipe',
|
||||||
encoding: 'utf-8'
|
encoding: 'utf-8'
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -32,7 +34,6 @@ export function tryGetRunCommandError(command: string, args: string[] = [], cwd?
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function connectService(
|
export async function connectService(
|
||||||
_client: RequestClientType,
|
|
||||||
option: McpOptions
|
option: McpOptions
|
||||||
): Promise<RestfulResponse> {
|
): Promise<RestfulResponse> {
|
||||||
try {
|
try {
|
||||||
@ -48,19 +49,25 @@ export async function connectService(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
client = await connect(option);
|
const client = await connect(option);
|
||||||
|
const uuid = randomUUID();
|
||||||
|
clientMap.set(uuid, client);
|
||||||
|
|
||||||
|
const versionInfo = client.getServerVersion();
|
||||||
|
|
||||||
const connectResult = {
|
const connectResult = {
|
||||||
code: 200,
|
code: 200,
|
||||||
msg: 'Connect to OpenMCP successfully\nWelcome back, Kirigaya'
|
msg: {
|
||||||
|
status: 'success',
|
||||||
|
clientId: uuid,
|
||||||
|
name: versionInfo?.name,
|
||||||
|
version: versionInfo?.version
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return connectResult;
|
return connectResult;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
console.log('meet error');
|
|
||||||
console.log(error);
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: 这边获取到的 error 不够精致,如何才能获取到更加精准的错误
|
// TODO: 这边获取到的 error 不够精致,如何才能获取到更加精准的错误
|
||||||
// 比如 error: Failed to spawn: `server.py`
|
// 比如 error: Failed to spawn: `server.py`
|
||||||
// Caused by: No such file or directory (os error 2)
|
// Caused by: No such file or directory (os error 2)
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import { Controller, RequestClientType } from "../common";
|
import { Controller } from "../common";
|
||||||
import { PostMessageble } from "../hook/adapter";
|
import { PostMessageble } from "../hook/adapter";
|
||||||
|
import { RequestData } from "../common/index.dto";
|
||||||
|
import { getClient } from "../mcp/connect.service";
|
||||||
import { systemPromptDB } from "../hook/db";
|
import { systemPromptDB } from "../hook/db";
|
||||||
import { loadTabSaveConfig, saveTabSaveConfig } from "./panel.service";
|
import { loadTabSaveConfig, saveTabSaveConfig } from "./panel.service";
|
||||||
|
|
||||||
export class PanelController {
|
export class PanelController {
|
||||||
@Controller('panel/save')
|
@Controller('panel/save')
|
||||||
async savePanel(client: RequestClientType, data: any, webview: PostMessageble) {
|
async savePanel(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
const serverInfo = client?.getServerVersion();
|
const serverInfo = client?.getServerVersion();
|
||||||
saveTabSaveConfig(serverInfo, data);
|
saveTabSaveConfig(serverInfo, data);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -15,11 +18,11 @@ export class PanelController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Controller('panel/load')
|
@Controller('panel/load')
|
||||||
async loadPanel(client: RequestClientType, data: any, webview: PostMessageble) {
|
async loadPanel(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
const serverInfo = client?.getServerVersion();
|
const serverInfo = client?.getServerVersion();
|
||||||
const config = loadTabSaveConfig(serverInfo);
|
const config = loadTabSaveConfig(serverInfo);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -28,7 +31,8 @@ export class PanelController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Controller('system-prompts/set')
|
@Controller('system-prompts/set')
|
||||||
async setSystemPrompt(client: RequestClientType, data: any, webview: PostMessageble) {
|
async setSystemPrompt(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
const { name, content } = data;
|
const { name, content } = data;
|
||||||
|
|
||||||
await systemPromptDB.insert({
|
await systemPromptDB.insert({
|
||||||
@ -44,7 +48,8 @@ export class PanelController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Controller('system-prompts/delete')
|
@Controller('system-prompts/delete')
|
||||||
async deleteSystemPrompt(client: RequestClientType, data: any, webview: PostMessageble) {
|
async deleteSystemPrompt(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
const { name } = data;
|
const { name } = data;
|
||||||
await systemPromptDB.delete(name);
|
await systemPromptDB.delete(name);
|
||||||
return {
|
return {
|
||||||
@ -54,7 +59,8 @@ export class PanelController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Controller('system-prompts/save')
|
@Controller('system-prompts/save')
|
||||||
async saveSystemPrompts(client: RequestClientType, data: any, webview: PostMessageble) {
|
async saveSystemPrompts(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
const { prompts } = data;
|
const { prompts } = data;
|
||||||
|
|
||||||
await Promise.all(prompts.map((prompt: any) => {
|
await Promise.all(prompts.map((prompt: any) => {
|
||||||
@ -72,8 +78,8 @@ export class PanelController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Controller('system-prompts/load')
|
@Controller('system-prompts/load')
|
||||||
async loadSystemPrompts(client: RequestClientType, data: any, webview: PostMessageble) {
|
async loadSystemPrompts(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
const queryPrompts = await systemPromptDB.findAll();
|
const queryPrompts = await systemPromptDB.findAll();
|
||||||
const prompts = [];
|
const prompts = [];
|
||||||
for (const prompt of queryPrompts) {
|
for (const prompt of queryPrompts) {
|
||||||
|
@ -29,13 +29,13 @@ const logger = pino({
|
|||||||
export type MessageHandler = (message: VSCodeMessage) => void;
|
export type MessageHandler = (message: VSCodeMessage) => void;
|
||||||
|
|
||||||
interface IStdioLaunchSignature {
|
interface IStdioLaunchSignature {
|
||||||
type: 'stdio';
|
type: 'STDIO';
|
||||||
commandString: string;
|
commandString: string;
|
||||||
cwd: string;
|
cwd: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISSELaunchSignature {
|
interface ISSELaunchSignature {
|
||||||
type: 'sse';
|
type: 'SSE';
|
||||||
url: string;
|
url: string;
|
||||||
oauth: string;
|
oauth: string;
|
||||||
}
|
}
|
||||||
@ -44,7 +44,7 @@ export type ILaunchSigature = IStdioLaunchSignature | ISSELaunchSignature;
|
|||||||
|
|
||||||
function refreshConnectionOption(envPath: string) {
|
function refreshConnectionOption(envPath: string) {
|
||||||
const defaultOption = {
|
const defaultOption = {
|
||||||
type: 'stdio',
|
type: 'STDIO',
|
||||||
command: 'mcp',
|
command: 'mcp',
|
||||||
args: ['run', 'main.py'],
|
args: ['run', 'main.py'],
|
||||||
cwd: '../server'
|
cwd: '../server'
|
||||||
@ -84,7 +84,7 @@ function updateConnectionOption(data: any) {
|
|||||||
|
|
||||||
if (data.connectionType === 'STDIO') {
|
if (data.connectionType === 'STDIO') {
|
||||||
const connectionItem = {
|
const connectionItem = {
|
||||||
type: 'stdio',
|
type: 'STDIO',
|
||||||
command: data.command,
|
command: data.command,
|
||||||
args: data.args,
|
args: data.args,
|
||||||
cwd: data.cwd.replace(/\\/g, '/')
|
cwd: data.cwd.replace(/\\/g, '/')
|
||||||
@ -93,7 +93,7 @@ function updateConnectionOption(data: any) {
|
|||||||
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
|
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
|
||||||
} else {
|
} else {
|
||||||
const connectionItem = {
|
const connectionItem = {
|
||||||
type: 'sse',
|
type: 'SSE',
|
||||||
url: data.url,
|
url: data.url,
|
||||||
oauth: data.oauth
|
oauth: data.oauth
|
||||||
};
|
};
|
||||||
@ -155,14 +155,14 @@ wss.on('connection', (ws: any) => {
|
|||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'web/launch-signature':
|
case 'web/launch-signature':
|
||||||
const launchResultMessage: ILaunchSigature = option.type === 'stdio' ?
|
const launchResultMessage: ILaunchSigature = option.type === 'STDIO' ?
|
||||||
{
|
{
|
||||||
type: 'stdio',
|
type: 'STDIO',
|
||||||
commandString: option.command + ' ' + option.args.join(' '),
|
commandString: option.command + ' ' + option.args.join(' '),
|
||||||
cwd: option.cwd || ''
|
cwd: option.cwd || ''
|
||||||
} :
|
} :
|
||||||
{
|
{
|
||||||
type: 'sse',
|
type: 'SSE',
|
||||||
url: option.url,
|
url: option.url,
|
||||||
oauth: option.oauth || ''
|
oauth: option.oauth || ''
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import { Controller, RequestClientType } from "../common";
|
import { Controller } from "../common";
|
||||||
import { PostMessageble } from "../hook/adapter";
|
import { PostMessageble } from "../hook/adapter";
|
||||||
|
import { RequestData } from "../common/index.dto";
|
||||||
|
import { getClient } from "../mcp/connect.service";
|
||||||
import { getTour, loadSetting, saveSetting, setTour } from "./setting.service";
|
import { getTour, loadSetting, saveSetting, setTour } from "./setting.service";
|
||||||
|
|
||||||
export class SettingController {
|
export class SettingController {
|
||||||
|
|
||||||
@Controller('setting/save')
|
@Controller('setting/save')
|
||||||
async saveSetting(client: RequestClientType, data: any, webview: PostMessageble) {
|
async saveSetting(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
saveSetting(data);
|
saveSetting(data);
|
||||||
console.log('Settings saved successfully');
|
console.log('Settings saved successfully');
|
||||||
|
|
||||||
@ -16,8 +19,8 @@ export class SettingController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Controller('setting/load')
|
@Controller('setting/load')
|
||||||
async loadSetting(client: RequestClientType, data: any, webview: PostMessageble) {
|
async loadSetting(data: RequestData, webview: PostMessageble) {
|
||||||
|
const client = getClient(data.clientId);
|
||||||
const config = loadSetting();
|
const config = loadSetting();
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -26,10 +29,8 @@ export class SettingController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Controller('setting/set-tour')
|
@Controller('setting/set-tour')
|
||||||
async setTourController(client: RequestClientType, data: any, webview: PostMessageble) {
|
async setTourController(data: any, webview: PostMessageble) {
|
||||||
|
|
||||||
const { userHasReadGuide } = data;
|
const { userHasReadGuide } = data;
|
||||||
|
|
||||||
setTour(userHasReadGuide);
|
setTour(userHasReadGuide);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -39,7 +40,7 @@ export class SettingController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Controller('setting/get-tour')
|
@Controller('setting/get-tour')
|
||||||
async getTourController(client: RequestClientType, data: any, webview: PostMessageble) {
|
async getTourController(data: any, webview: PostMessageble) {
|
||||||
|
|
||||||
const { userHasReadGuide } = getTour();
|
const { userHasReadGuide } = getTour();
|
||||||
|
|
||||||
|
@ -9,7 +9,8 @@
|
|||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
"experimentalDecorators": true
|
"experimentalDecorators": true,
|
||||||
|
"moduleResolution": "node"
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": [
|
||||||
|
5437
software/package-lock.json
generated
5437
software/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "openmcp-electron",
|
"name": "@openmcp/electron",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
@ -13,6 +13,7 @@
|
|||||||
"email": "1193466151@qq.com"
|
"email": "1193466151@qq.com"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@openmcp/service": "*",
|
||||||
"@modelcontextprotocol/sdk": "^1.10.2",
|
"@modelcontextprotocol/sdk": "^1.10.2",
|
||||||
"@seald-io/nedb": "^4.1.1",
|
"@seald-io/nedb": "^4.1.1",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
|
@ -40,14 +40,14 @@ function createWindow(): void {
|
|||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'electron/launch-signature':
|
case 'electron/launch-signature':
|
||||||
const launchResultMessage: ILaunchSigature = option.type === 'stdio' ?
|
const launchResultMessage: ILaunchSigature = option.type === 'STDIO' ?
|
||||||
{
|
{
|
||||||
type: 'stdio',
|
type: 'STDIO',
|
||||||
commandString: option.command + ' ' + option.args.join(' '),
|
commandString: option.command + ' ' + option.args.join(' '),
|
||||||
cwd: option.cwd || ''
|
cwd: option.cwd || ''
|
||||||
} :
|
} :
|
||||||
{
|
{
|
||||||
type: 'sse',
|
type: 'SSE',
|
||||||
url: option.url,
|
url: option.url,
|
||||||
oauth: option.oauth || ''
|
oauth: option.oauth || ''
|
||||||
};
|
};
|
||||||
|
@ -24,13 +24,13 @@ export class ElectronIPCLike {
|
|||||||
|
|
||||||
|
|
||||||
interface IStdioLaunchSignature {
|
interface IStdioLaunchSignature {
|
||||||
type: 'stdio';
|
type: 'STDIO';
|
||||||
commandString: string;
|
commandString: string;
|
||||||
cwd: string;
|
cwd: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISSELaunchSignature {
|
interface ISSELaunchSignature {
|
||||||
type:'sse';
|
type:'SSE';
|
||||||
url: string;
|
url: string;
|
||||||
oauth: string;
|
oauth: string;
|
||||||
}
|
}
|
||||||
@ -39,7 +39,7 @@ export type ILaunchSigature = IStdioLaunchSignature | ISSELaunchSignature;
|
|||||||
|
|
||||||
export function refreshConnectionOption(envPath: string) {
|
export function refreshConnectionOption(envPath: string) {
|
||||||
const defaultOption = {
|
const defaultOption = {
|
||||||
type:'stdio',
|
type:'STDIO',
|
||||||
command: 'mcp',
|
command: 'mcp',
|
||||||
args: ['run', 'main.py'],
|
args: ['run', 'main.py'],
|
||||||
cwd: '../server'
|
cwd: '../server'
|
||||||
@ -80,7 +80,7 @@ export function updateConnectionOption(data: any) {
|
|||||||
|
|
||||||
if (data.connectionType === 'STDIO') {
|
if (data.connectionType === 'STDIO') {
|
||||||
const connectionItem = {
|
const connectionItem = {
|
||||||
type: 'stdio',
|
type: 'STDIO',
|
||||||
command: data.command,
|
command: data.command,
|
||||||
args: data.args,
|
args: data.args,
|
||||||
cwd: data.cwd.replace(/\\/g, '/')
|
cwd: data.cwd.replace(/\\/g, '/')
|
||||||
@ -89,7 +89,7 @@ export function updateConnectionOption(data: any) {
|
|||||||
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
|
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
|
||||||
} else {
|
} else {
|
||||||
const connectionItem = {
|
const connectionItem = {
|
||||||
type: 'sse',
|
type: 'SSE',
|
||||||
url: data.url,
|
url: data.url,
|
||||||
oauth: data.oauth
|
oauth: data.oauth
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as OpenMCPService from '../openmcp-sdk/service';
|
import * as OpenMCPService from '@openmcp/service';
|
||||||
import { launch } from './common/entry';
|
import { launch } from './common/entry';
|
||||||
|
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
|
@ -7,7 +7,7 @@ export type FsPath = string;
|
|||||||
export const panels = new Map<FsPath, vscode.WebviewPanel>();
|
export const panels = new Map<FsPath, vscode.WebviewPanel>();
|
||||||
|
|
||||||
export interface IStdioConnectionItem {
|
export interface IStdioConnectionItem {
|
||||||
type: 'stdio';
|
type: 'STDIO';
|
||||||
name: string;
|
name: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
command: string;
|
command: string;
|
||||||
@ -18,7 +18,7 @@ export interface IStdioConnectionItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ISSEConnectionItem {
|
export interface ISSEConnectionItem {
|
||||||
type: 'sse';
|
type: 'SSE';
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
url: string;
|
url: string;
|
||||||
@ -29,13 +29,13 @@ export interface ISSEConnectionItem {
|
|||||||
|
|
||||||
|
|
||||||
interface IStdioLaunchSignature {
|
interface IStdioLaunchSignature {
|
||||||
type: 'stdio';
|
type: 'STDIO';
|
||||||
commandString: string;
|
commandString: string;
|
||||||
cwd: string;
|
cwd: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISSELaunchSignature {
|
interface ISSELaunchSignature {
|
||||||
type:'sse';
|
type:'SSE';
|
||||||
url: string;
|
url: string;
|
||||||
oauth: string;
|
oauth: string;
|
||||||
}
|
}
|
||||||
@ -123,7 +123,7 @@ export function getWorkspaceConnectionConfig() {
|
|||||||
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 (item.type === '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, '/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,7 +169,7 @@ export function saveWorkspaceConnectionConfig(workspace: string) {
|
|||||||
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, '/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,7 +213,7 @@ export function updateWorkspaceConnectionConfig(
|
|||||||
|
|
||||||
if (data.connectionType === 'STDIO') {
|
if (data.connectionType === 'STDIO') {
|
||||||
const connectionItem: IStdioConnectionItem = {
|
const connectionItem: IStdioConnectionItem = {
|
||||||
type: 'stdio',
|
type: 'STDIO',
|
||||||
name: data.serverInfo.name,
|
name: data.serverInfo.name,
|
||||||
version: data.serverInfo.version,
|
version: data.serverInfo.version,
|
||||||
command: data.command,
|
command: data.command,
|
||||||
@ -234,7 +234,7 @@ export function updateWorkspaceConnectionConfig(
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
const connectionItem: ISSEConnectionItem = {
|
const connectionItem: ISSEConnectionItem = {
|
||||||
type: 'sse',
|
type: 'SSE',
|
||||||
name: data.serverInfo.name,
|
name: data.serverInfo.name,
|
||||||
version: data.serverInfo.version,
|
version: data.serverInfo.version,
|
||||||
url: data.url,
|
url: data.url,
|
||||||
@ -267,7 +267,7 @@ export function updateInstalledConnectionConfig(
|
|||||||
|
|
||||||
if (data.connectionType === 'STDIO') {
|
if (data.connectionType === 'STDIO') {
|
||||||
const connectionItem: IStdioConnectionItem = {
|
const connectionItem: IStdioConnectionItem = {
|
||||||
type: 'stdio',
|
type: 'STDIO',
|
||||||
name: data.serverInfo.name,
|
name: data.serverInfo.name,
|
||||||
version: data.serverInfo.version,
|
version: data.serverInfo.version,
|
||||||
command: data.command,
|
command: data.command,
|
||||||
@ -287,7 +287,7 @@ export function updateInstalledConnectionConfig(
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
const connectionItem: ISSEConnectionItem = {
|
const connectionItem: ISSEConnectionItem = {
|
||||||
type: 'sse',
|
type: 'SSE',
|
||||||
name: data.serverInfo.name,
|
name: data.serverInfo.name,
|
||||||
version: data.serverInfo.version,
|
version: data.serverInfo.version,
|
||||||
url: data.url,
|
url: data.url,
|
||||||
|
@ -52,7 +52,7 @@ export async function validateAndGetCommandPath(commandString: string, cwd?: str
|
|||||||
|
|
||||||
export async function acquireInstalledConnection(): Promise<IConnectionItem | undefined> {
|
export async function acquireInstalledConnection(): Promise<IConnectionItem | undefined> {
|
||||||
// 让用户选择连接类型
|
// 让用户选择连接类型
|
||||||
const connectionType = await vscode.window.showQuickPick(['stdio', 'sse'], {
|
const connectionType = await vscode.window.showQuickPick(['STDIO', 'SSE'], {
|
||||||
placeHolder: '请选择连接类型',
|
placeHolder: '请选择连接类型',
|
||||||
canPickMany: false
|
canPickMany: false
|
||||||
});
|
});
|
||||||
@ -61,7 +61,7 @@ export async function acquireInstalledConnection(): Promise<IConnectionItem | un
|
|||||||
return; // 用户取消选择
|
return; // 用户取消选择
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectionType === 'stdio') {
|
if (connectionType === 'STDIO') {
|
||||||
// 获取 command
|
// 获取 command
|
||||||
const commandString = await vscode.window.showInputBox({
|
const commandString = await vscode.window.showInputBox({
|
||||||
prompt: '请输入连接的 command',
|
prompt: '请输入连接的 command',
|
||||||
@ -97,15 +97,15 @@ export async function acquireInstalledConnection(): Promise<IConnectionItem | un
|
|||||||
|
|
||||||
// 保存连接配置
|
// 保存连接配置
|
||||||
return {
|
return {
|
||||||
type: 'stdio',
|
type: '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',
|
||||||
@ -124,8 +124,8 @@ export async function acquireInstalledConnection(): Promise<IConnectionItem | un
|
|||||||
|
|
||||||
// 保存连接配置
|
// 保存连接配置
|
||||||
return {
|
return {
|
||||||
type: 'sse',
|
type: '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 || ''
|
||||||
|
@ -6,7 +6,7 @@ import * as vscode from 'vscode';
|
|||||||
|
|
||||||
export async function acquireUserCustomConnection(): Promise<IConnectionItem | undefined> {
|
export async function acquireUserCustomConnection(): Promise<IConnectionItem | undefined> {
|
||||||
// 让用户选择连接类型
|
// 让用户选择连接类型
|
||||||
const connectionType = await vscode.window.showQuickPick(['stdio', 'sse'], {
|
const connectionType = await vscode.window.showQuickPick(['STDIO', 'SSE'], {
|
||||||
placeHolder: '请选择连接类型'
|
placeHolder: '请选择连接类型'
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ export async function acquireUserCustomConnection(): Promise<IConnectionItem | u
|
|||||||
return; // 用户取消选择
|
return; // 用户取消选择
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectionType === 'stdio') {
|
if (connectionType === 'STDIO') {
|
||||||
// 获取 command
|
// 获取 command
|
||||||
const commandString = await vscode.window.showInputBox({
|
const commandString = await vscode.window.showInputBox({
|
||||||
prompt: '请输入连接的 command',
|
prompt: '请输入连接的 command',
|
||||||
@ -47,15 +47,15 @@ export async function acquireUserCustomConnection(): Promise<IConnectionItem | u
|
|||||||
|
|
||||||
// 保存连接配置
|
// 保存连接配置
|
||||||
return {
|
return {
|
||||||
type: 'stdio',
|
type: 'STDIO',
|
||||||
name: `stdio-${Date.now()}`,
|
name: `STDIO-${Date.now()}`,
|
||||||
command: command,
|
command: command,
|
||||||
args,
|
args,
|
||||||
cwd: cwd || '',
|
cwd: cwd || '',
|
||||||
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',
|
||||||
@ -74,8 +74,8 @@ export async function acquireUserCustomConnection(): Promise<IConnectionItem | u
|
|||||||
|
|
||||||
// 保存连接配置
|
// 保存连接配置
|
||||||
return {
|
return {
|
||||||
type: 'sse',
|
type: '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 || ''
|
||||||
|
@ -19,7 +19,7 @@ export class WebviewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
revealOpenMcpWebviewPanel(context, 'workspace', uri.fsPath, {
|
revealOpenMcpWebviewPanel(context, 'workspace', uri.fsPath, {
|
||||||
type: 'stdio',
|
type: 'STDIO',
|
||||||
name: 'OpenMCP',
|
name: 'OpenMCP',
|
||||||
command: sigature.command,
|
command: sigature.command,
|
||||||
args: sigature.args,
|
args: sigature.args,
|
||||||
|
@ -2,7 +2,7 @@ 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 { IConnectionItem, ILaunchSigature, panels, updateInstalledConnectionConfig, updateWorkspaceConnectionConfig } from '../global';
|
||||||
import * as OpenMCPService from '../../openmcp-sdk/service';
|
import * as OpenMCPService from '@openmcp/service';
|
||||||
|
|
||||||
export function getWebviewContent(context: vscode.ExtensionContext, panel: vscode.WebviewPanel): string | undefined {
|
export function getWebviewContent(context: vscode.ExtensionContext, panel: vscode.WebviewPanel): string | undefined {
|
||||||
const viewRoot = fspath.join(context.extensionPath, 'openmcp-sdk', 'renderer');
|
const viewRoot = fspath.join(context.extensionPath, 'openmcp-sdk', 'renderer');
|
||||||
@ -29,7 +29,7 @@ export function revealOpenMcpWebviewPanel(
|
|||||||
type: 'workspace' | 'installed',
|
type: 'workspace' | 'installed',
|
||||||
panelKey: string,
|
panelKey: string,
|
||||||
option: IConnectionItem = {
|
option: IConnectionItem = {
|
||||||
type: 'stdio',
|
type: 'STDIO',
|
||||||
name: 'OpenMCP',
|
name: 'OpenMCP',
|
||||||
command: 'mcp',
|
command: 'mcp',
|
||||||
args: ['run', 'main.py']
|
args: ['run', 'main.py']
|
||||||
@ -68,14 +68,14 @@ export function revealOpenMcpWebviewPanel(
|
|||||||
// 拦截消息,注入额外信息
|
// 拦截消息,注入额外信息
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'vscode/launch-signature':
|
case 'vscode/launch-signature':
|
||||||
const launchResultMessage: ILaunchSigature = option.type === 'stdio' ?
|
const launchResultMessage: ILaunchSigature = option.type === 'STDIO' ?
|
||||||
{
|
{
|
||||||
type: 'stdio',
|
type: 'STDIO',
|
||||||
commandString: option.command + ' ' + option.args.join(' '),
|
commandString: option.command + ' ' + option.args.join(' '),
|
||||||
cwd: option.cwd || ''
|
cwd: option.cwd || ''
|
||||||
} :
|
} :
|
||||||
{
|
{
|
||||||
type: 'sse',
|
type: 'SSE',
|
||||||
url: option.url,
|
url: option.url,
|
||||||
oauth: option.oauth || ''
|
oauth: option.oauth || ''
|
||||||
};
|
};
|
||||||
|
22
turbo.json
Normal file
22
turbo.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://turborepo.com/schema.json",
|
||||||
|
"tasks": {
|
||||||
|
"dev": {
|
||||||
|
"persistent": true,
|
||||||
|
"cache": false
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
},
|
||||||
|
"@openmcp/electron#dev": {
|
||||||
|
"dependsOn": ["@openmcp/renderer#build", "@openmcp/service#build"],
|
||||||
|
"persistent": true,
|
||||||
|
"cache": false
|
||||||
|
},
|
||||||
|
"@openmcp/electron#build": {
|
||||||
|
"dependsOn": ["@openmcp/renderer#build", "@openmcp/service#build"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user