Compare commits
No commits in common. "c218dcba03a2c0036b5b94f07cacade9f2448ba5" and "a1bcd06acd0d9e81eb4fc8fd5c5143eb0019ea1b" have entirely different histories.
c218dcba03
...
a1bcd06acd
3
.gitignore
vendored
3
.gitignore
vendored
@ -13,5 +13,4 @@ resources/ocr/*.js
|
||||
resources/ocr/*.wasm
|
||||
resources/renderer
|
||||
resources/service
|
||||
*.traineddata
|
||||
.turbo
|
||||
*.traineddata
|
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@ -10,10 +10,7 @@
|
||||
},
|
||||
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
|
||||
"typescript.tsc.autoDetect": "off",
|
||||
|
||||
|
||||
"i18n-haru.root": "renderer/src/i18n",
|
||||
"i18n-haru.main": "zh",
|
||||
"i18n-ally.localesPaths": [
|
||||
"renderer/src/i18n"
|
||||
]
|
||||
"i18n-haru.main": "zh"
|
||||
}
|
||||
|
28
.vscode/tabs.example-servers_puppeteer.json
vendored
28
.vscode/tabs.example-servers_puppeteer.json
vendored
@ -331,34 +331,6 @@
|
||||
"state": "success",
|
||||
"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": {
|
||||
|
10
CHANGELOG.md
10
CHANGELOG.md
@ -1,14 +1,8 @@
|
||||
# Change Log
|
||||
|
||||
## [main] 0.1.0
|
||||
- 新特性:支持同时连入多个 mcp server
|
||||
- 新特性:更新协议内容,支持 streamable http 协议,未来将逐步取代 SSE 的连接方式
|
||||
- 对于 uv 创建的 py 项目进行特殊支持:自动初始化项目,并将 mcp 定向到 .venv/bin/mcp 中,不再需要用户全局安装 mcp
|
||||
- 对于 npm 创建的 js/ts 项目进行特殊支持:自动初始化项目
|
||||
|
||||
## [main] 0.0.9
|
||||
- 修复 0.0.8 引入的bug:system prompt 返回的是索引而非真实内容
|
||||
- 测试新的发布管线
|
||||
-
|
||||
|
||||
## [main] 0.0.8
|
||||
- 大模型 API 测试时更加完整的报错
|
||||
@ -69,4 +63,4 @@
|
||||
- 完成配置加载,保存,大模型设置
|
||||
- 完成标签页自动保存
|
||||
- 完成大模型对话窗口和工具调用
|
||||
- 完成对 vscode 和 trae 的支持
|
||||
- 完成对 vscode 和 trae 的支持
|
16
README.md
16
README.md
@ -136,13 +136,19 @@ B <--mcp--> m(MCP Server)
|
||||
配置项目
|
||||
|
||||
```bash
|
||||
npm run setup
|
||||
## linux
|
||||
./configure.sh
|
||||
## windows
|
||||
./configure.ps1
|
||||
```
|
||||
|
||||
启动 dev server
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
## linux
|
||||
./dev.sh
|
||||
## windows
|
||||
./dev.ps1
|
||||
```
|
||||
|
||||
> 端口占用: 8282 (renderer) + 8081 (service)
|
||||
@ -157,7 +163,11 @@ B <--mcp--> m(MCP Server)
|
||||
|
||||
负载部署
|
||||
```bash
|
||||
npm run build
|
||||
## linux
|
||||
./build_service.sh
|
||||
|
||||
## windows
|
||||
./build_service.ps1
|
||||
```
|
||||
|
||||
and just press f5, いただきます
|
||||
|
38
build_service.ps1
Normal file
38
build_service.ps1
Normal file
@ -0,0 +1,38 @@
|
||||
# 创建并清理资源目录
|
||||
New-Item -ItemType Directory -Path ./openmcp-sdk -Force
|
||||
Remove-Item -Recurse -Force ./openmcp-sdk/* -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Path ./openmcp-sdk -Force
|
||||
|
||||
# 获取当前工作目录的绝对路径
|
||||
$currentDir = (Get-Location).Path
|
||||
|
||||
# 并行构建 renderer 和 service
|
||||
$rendererJob = Start-Job -ScriptBlock {
|
||||
param($workDir)
|
||||
Set-Location -Path "$workDir\renderer"
|
||||
npm run build
|
||||
Move-Item -Path "./dist" -Destination "$workDir\openmcp-sdk\renderer" -Force
|
||||
} -ArgumentList $currentDir
|
||||
|
||||
$serviceJob = Start-Job -ScriptBlock {
|
||||
param($workDir)
|
||||
Set-Location -Path "$workDir\service"
|
||||
npm run build
|
||||
Move-Item -Path "./dist" -Destination "$workDir\openmcp-sdk\service" -Force
|
||||
} -ArgumentList $currentDir
|
||||
|
||||
# 等待任务完成
|
||||
$rendererJob | Wait-Job | Receive-Job
|
||||
$serviceJob | Wait-Job | Receive-Job
|
||||
|
||||
# 将 openmcp-sdk 目录复制到 software/openmcp-sdk
|
||||
New-Item -ItemType Directory -Path ./software/openmcp-sdk -Force
|
||||
Remove-Item -Recurse -Force ./software/openmcp-sdk/* -ErrorAction SilentlyContinue
|
||||
Copy-Item -Recurse -Path ./openmcp-sdk -Destination ./software/ -Force
|
||||
|
||||
$serviceJob = Start-Job -ScriptBlock {
|
||||
param($workDir)
|
||||
npm run build:task-loop
|
||||
} -ArgumentList $currentDir
|
||||
|
||||
Write-Output "finish building services in ./openmcp-sdk"
|
18
build_service.sh
Executable file
18
build_service.sh
Executable file
@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
mkdir -p ./openmcp-sdk
|
||||
rm -rf ./openmcp-sdk/
|
||||
mkdir -p ./openmcp-sdk
|
||||
|
||||
(cd ./renderer && npm run build && mv ./dist ../openmcp-sdk/renderer) &
|
||||
(cd ./service && npm run build && mv ./dist ../openmcp-sdk/service) &
|
||||
|
||||
wait
|
||||
|
||||
mkdir -p ./software/openmcp-sdk
|
||||
rm -rf ./software/openmcp-sdk
|
||||
cp -r ./openmcp-sdk ./software/
|
||||
|
||||
npm run build:task-loop
|
||||
|
||||
echo "finish building services in ./openmcp-sdk"
|
17
configure.ps1
Normal file
17
configure.ps1
Normal file
@ -0,0 +1,17 @@
|
||||
# 安装 renderer 依赖
|
||||
Set-Location renderer
|
||||
npm i
|
||||
Set-Location ..
|
||||
|
||||
# 安装 service 依赖并打补丁
|
||||
Set-Location service
|
||||
npm i
|
||||
node patch-mcp-sdk.js
|
||||
Set-Location ..
|
||||
|
||||
Set-Location servers
|
||||
uv sync
|
||||
Set-Location ..
|
||||
|
||||
# 安装根目录依赖
|
||||
npm i
|
5
configure.sh
Executable file
5
configure.sh
Executable file
@ -0,0 +1,5 @@
|
||||
cd renderer && npm i && cd ..
|
||||
cd service && npm i && cd ..
|
||||
cd servers && uv sync
|
||||
npm i
|
||||
npm run prepare:ocr
|
7
dev.ps1
Normal file
7
dev.ps1
Normal file
@ -0,0 +1,7 @@
|
||||
npx concurrently `
|
||||
-n "renderer,service" `
|
||||
-p " {name} " `
|
||||
-c "black.bgBlue,black.bgGreen" `
|
||||
--kill-others `
|
||||
"cd renderer && npm run serve" `
|
||||
"cd service && npm run serve"
|
9
dev.sh
Executable file
9
dev.sh
Executable file
@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
npx concurrently \
|
||||
-n "renderer,service" \
|
||||
-p " {name} " \
|
||||
-c "black.bgBlue,black.bgGreen" \
|
||||
--kill-others \
|
||||
"cd renderer && npm run serve" \
|
||||
"cd service && npm run serve"
|
8577
package-lock.json
generated
8577
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@ -2,7 +2,7 @@
|
||||
"name": "openmcp",
|
||||
"displayName": "OpenMCP",
|
||||
"description": "An all in one MCP Client/TestTool",
|
||||
"version": "0.1.0",
|
||||
"version": "0.0.8",
|
||||
"publisher": "kirigaya",
|
||||
"author": {
|
||||
"name": "kirigaya",
|
||||
@ -212,17 +212,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"workspaces": [
|
||||
"service",
|
||||
"renderer",
|
||||
"software"
|
||||
],
|
||||
"scripts": {
|
||||
"setup": "npm i && npm run prepare:ocr",
|
||||
"serve": "turbo serve",
|
||||
"build": "turbo build --filter=!@openmcp/electron",
|
||||
"build:electron": "turbo build --filter=@openmcp/electron",
|
||||
"build:all": "turbo build",
|
||||
"vscode:prepublish": "webpack --mode production",
|
||||
"compile": "tsc -p ./",
|
||||
"watch": "tsc -watch -p ./",
|
||||
@ -252,10 +242,8 @@
|
||||
"fork-ts-checker-webpack-plugin": "^9.1.0",
|
||||
"null-loader": "^4.0.1",
|
||||
"ts-loader": "^9.5.1",
|
||||
"turbo": "^2.5.3",
|
||||
"typescript": "^5.4.2",
|
||||
"webpack": "^5.99.5",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"packageManager": "npm@10.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +1 @@
|
||||
VITE_USE_AUTH=false
|
||||
VITE_WEBSOCKET_URL=ws://localhost:8282
|
@ -1,2 +1 @@
|
||||
VITE_USE_AUTH=false
|
||||
VITE_WEBSOCKET_URL=ws://localhost:8282
|
@ -1,18 +1,33 @@
|
||||
## dev
|
||||
# test-vite
|
||||
|
||||
如果想要部署到公网中,想要通过密码认证才能进入,进行如下步骤:
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
```bash
|
||||
touch .env.website.local
|
||||
## Recommended IDE Setup
|
||||
|
||||
[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
|
||||
|
||||
```toml
|
||||
VITE_USE_AUTH=true
|
||||
VITE_WEBSOCKET_URL=wss://<IP>/<路径>
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
使用 `npm run serve:website` 进行测试(服务端使用 ts-node src/server.ts)
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
使用 `npm run build:website` 进行打包
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
4457
renderer/package-lock.json
generated
Normal file
4457
renderer/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,9 @@
|
||||
{
|
||||
"name": "@openmcp/renderer",
|
||||
"version": "0.1.0",
|
||||
"name": "test-vite",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"postbuild": "node scripts/post-build.mjs",
|
||||
"lint": "vue-cli-service lint",
|
||||
"serve": "vite",
|
||||
"serve:website": "vite --mode website",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
@ -31,26 +28,13 @@
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.1",
|
||||
"@babel/plugin-proposal-decorators": "^7.27.1",
|
||||
"@babel/plugin-syntax-import-attributes": "^7.27.1",
|
||||
"@babel/plugin-syntax-import-meta": "^7.10.4",
|
||||
"@babel/plugin-transform-typescript": "^7.27.1",
|
||||
"@originjs/vite-plugin-commonjs": "^1.0.3",
|
||||
"@rollup/pluginutils": "^5.1.4",
|
||||
"@tsconfig/node22": "^22.0.1",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"@vue/babel-plugin-jsx": "^1.4.0",
|
||||
"@vue/devtools-core": "^7.7.6",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"error-stack-parser-es": "^0.1.5",
|
||||
"kolorist": "^1.8.0",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"rollup": "^4.41.0",
|
||||
"sirv": "^3.0.1",
|
||||
"typescript": "~5.8.0",
|
||||
"vite": "^6.2.4",
|
||||
"vite-plugin-vue-devtools": "^7.7.2",
|
||||
|
Binary file not shown.
@ -1,43 +0,0 @@
|
||||
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>
|
||||
|
||||
<Tour v-if="!userHasReadGuide"/>
|
||||
<PasswordDialog v-if="useAuth"/>
|
||||
<PasswordDialog v-if="password"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -18,7 +18,8 @@ import MainPanel from '@/components/main-panel/index.vue';
|
||||
import { setDefaultCss } from './hook/css';
|
||||
import { greenLog, pinkLog } from './views/setting/util';
|
||||
import { useMessageBridge } from './api/message-bridge';
|
||||
import { initialise } from './views/connect';
|
||||
import { initialise } from './views/connect/connection';
|
||||
import { getPlatform } from './api/platform';
|
||||
import Tour from '@/components/guide/tour.vue';
|
||||
import { userHasReadGuide } from './components/guide/tour';
|
||||
|
||||
@ -36,10 +37,8 @@ bridge.addCommandListener('hello', data => {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const useAuth = Boolean(import.meta.env.VITE_USE_AUTH !== "false");
|
||||
console.log(import.meta.env.VITE_USE_AUTH, useAuth);
|
||||
|
||||
privilegeStatus.allow = !Boolean(useAuth);
|
||||
const password = Boolean(import.meta.env.VITE_USE_PASSWORD);
|
||||
privilegeStatus.allow = !Boolean(password);
|
||||
|
||||
onMounted(async () => {
|
||||
// 初始化 css
|
||||
@ -48,11 +47,11 @@ onMounted(async () => {
|
||||
pinkLog('OpenMCP Client 启动');
|
||||
|
||||
// 跳转到首页
|
||||
// if (route.name !== 'debug') {
|
||||
// const targetRoute = import.meta.env.BASE_URL + 'debug';
|
||||
// console.log('go to ' + targetRoute);
|
||||
// router.push(targetRoute);
|
||||
// }
|
||||
if (route.name !== 'debug') {
|
||||
const targetRoute = import.meta.env.BASE_URL + 'debug';
|
||||
console.log('go to ' + targetRoute);
|
||||
router.push(targetRoute);
|
||||
}
|
||||
|
||||
// 进行桥接
|
||||
await bridge.awaitForWebsocket();
|
||||
|
@ -7,9 +7,9 @@ export interface VSCodeMessage {
|
||||
callbackId?: string;
|
||||
}
|
||||
|
||||
export interface RestFulResponse<T = any> {
|
||||
export interface RestFulResponse {
|
||||
code: number;
|
||||
msg: T;
|
||||
msg: any;
|
||||
}
|
||||
|
||||
export type MessageHandler = (message: VSCodeMessage) => void;
|
||||
@ -19,11 +19,6 @@ interface AddCommandListenerOption {
|
||||
once: boolean // 只调用一次就销毁
|
||||
}
|
||||
|
||||
export interface ICommandRequestData {
|
||||
clientId?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export class MessageBridge {
|
||||
private ws: WebSocket | null = null;
|
||||
private handlers = new Map<string, Set<CommandHandler>>();
|
||||
@ -211,7 +206,7 @@ export class MessageBridge {
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
public commandRequest<T = any>(command: string, data?: ICommandRequestData): Promise<RestFulResponse<T>> {
|
||||
public commandRequest(command: string, data?: any) {
|
||||
return new Promise<RestFulResponse>((resolve, reject) => {
|
||||
this.addCommandListener(command, (data) => {
|
||||
resolve(data as RestFulResponse);
|
||||
|
@ -58,7 +58,7 @@
|
||||
</el-tour-step>
|
||||
|
||||
<el-tour-step
|
||||
:target="client.connectionSettingRef"
|
||||
:target="connectionSettingRef"
|
||||
:prev-button-props="{ children: '上一步' }"
|
||||
:next-button-props="{ children: '下一步' }"
|
||||
:show-close="false"
|
||||
@ -78,7 +78,7 @@
|
||||
</el-tour-step>
|
||||
|
||||
<el-tour-step
|
||||
:target="client.connectionLogRef"
|
||||
:target="connectionLogRef"
|
||||
:prev-button-props="{ children: '上一步' }"
|
||||
:next-button-props="{ children: '下一步' }"
|
||||
:show-close="false"
|
||||
@ -249,17 +249,16 @@ import TourTitle from './tour-title.vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { welcomeRef } from '@/views/debug/welcome';
|
||||
import { connectionLogRef, connectionSettingRef } from '@/views/connect/connection';
|
||||
import { llmSettingRef } from '@/views/setting/api';
|
||||
import { userHasReadGuide } from './tour';
|
||||
import { setTour } from '@/hook/setting';
|
||||
import { mcpClientAdapter } from '@/views/connect/core';
|
||||
|
||||
const openTour = ref(true);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const router = useRouter();
|
||||
const client = mcpClientAdapter.masterNode;
|
||||
|
||||
const baseUrl = import.meta.env.BASE_URL;
|
||||
|
||||
|
@ -44,8 +44,6 @@ import { panelLoaded } from '@/hook/panel';
|
||||
|
||||
defineComponent({ name: 'main-panel' });
|
||||
|
||||
const baseURL = import.meta.env.BASE_URL;
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
@ -54,7 +52,7 @@ function pageAddNewTab() {
|
||||
|
||||
// 如果当前不在 debug 路由,则切换到 debug 路由
|
||||
if (route.name !== 'debug') {
|
||||
router.push(baseURL + 'debug');
|
||||
router.replace('/debug');
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,7 +61,7 @@ function setActiveTab(index: number) {
|
||||
tabs.activeIndex = index;
|
||||
// 如果不在 debug 路由,则进入
|
||||
if (route.name !== 'debug') {
|
||||
router.push(baseURL + 'debug');
|
||||
router.replace('/debug');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,7 @@
|
||||
import { defineComponent, defineProps, computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { tabs } from '../panel';
|
||||
import type { ResourceStorage } from './resources';
|
||||
import { ResourceStorage } from './resources';
|
||||
import { getImageBlobUrlByBase64 } from '@/hook/util';
|
||||
import JsonRender from '@/components/json-render/index.vue';
|
||||
|
||||
|
@ -18,7 +18,7 @@ import { ref, onMounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { privilegeStatus } from './status';
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
import { initialise } from '@/views/connect';
|
||||
import { initialise } from '@/views/connect/connection';
|
||||
|
||||
const dialogVisible = ref(true);
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="connected-status-container"
|
||||
id="connected-status-container"
|
||||
@click.stop="toggleConnectionPanel()"
|
||||
:class="{ 'connected': client.connectionResult.success }"
|
||||
:class="{ 'connected': connectionResult.success }"
|
||||
>
|
||||
<span class="mcp-server-info">
|
||||
<el-tooltip
|
||||
@ -15,7 +15,7 @@
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<span class="connect-status">
|
||||
<span v-if="client.connectionResult.success">
|
||||
<span v-if="connectionResult.success">
|
||||
<span class="iconfont icon-connect"></span>
|
||||
<span class="iconfont icon-dui"></span>
|
||||
</span>
|
||||
@ -32,19 +32,18 @@
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { Connection } from './sidebar';
|
||||
import { mcpClientAdapter } from '@/views/connect/core';
|
||||
import { connectionResult } from '@/views/connect/connection';
|
||||
|
||||
defineComponent({ name: 'connected' });
|
||||
|
||||
const { t } = useI18n();
|
||||
const client = mcpClientAdapter.masterNode;
|
||||
|
||||
const fullDisplayServerName = computed(() => {
|
||||
return client.connectionResult.name + '/' + client.connectionResult.version;
|
||||
return connectionResult.serverInfo.name + '/' + connectionResult.serverInfo.version;
|
||||
});
|
||||
|
||||
const displayServerName = computed(() => {
|
||||
const name = client.connectionResult.name;
|
||||
const name = connectionResult.serverInfo.name;
|
||||
if (name.length <= 3) return name;
|
||||
|
||||
// 处理中文混合名称
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { useMessageBridge } from "@/api/message-bridge";
|
||||
import { pinkLog } from "@/views/setting/util";
|
||||
import { debugModes, tabs } from "@/components/main-panel/panel";
|
||||
import { markRaw, ref } from "vue";
|
||||
import { markRaw, ref, nextTick } from "vue";
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import type { McpClient } from "@/views/connect/core";
|
||||
|
||||
interface SaveTabItem {
|
||||
name: string;
|
||||
@ -13,58 +12,67 @@ interface SaveTabItem {
|
||||
storage: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface SaveTab {
|
||||
interface SaveTab {
|
||||
tabs: SaveTabItem[]
|
||||
currentIndex: number
|
||||
}
|
||||
|
||||
export const panelLoaded = ref(false);
|
||||
|
||||
export async function loadPanels(client: McpClient) {
|
||||
const bridge = useMessageBridge();
|
||||
const { code, msg } = await bridge.commandRequest<SaveTab>('panel/load', {
|
||||
clientId: client.clientId
|
||||
});
|
||||
if (code !== 200) {
|
||||
pinkLog('tabs 加载失败');
|
||||
console.log(msg);
|
||||
export function loadPanels() {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const bridge = useMessageBridge();
|
||||
|
||||
bridge.addCommandListener('panel/load', data => {
|
||||
if (data.code !== 200) {
|
||||
pinkLog('tabs 加载失败');
|
||||
console.log(data.msg);
|
||||
|
||||
} else {
|
||||
const persistTab = data.msg as SaveTab;
|
||||
|
||||
pinkLog('tabs 加载成功');
|
||||
|
||||
} else {
|
||||
const persistTab = msg;
|
||||
|
||||
pinkLog('tabs 加载成功');
|
||||
|
||||
if (persistTab.tabs.length === 0) {
|
||||
// 空的,直接返回不需要管
|
||||
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;
|
||||
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'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let debounceHandler: number;
|
||||
let debounceHandler: NodeJS.Timeout;
|
||||
|
||||
export function safeSavePanels() {
|
||||
clearTimeout(debounceHandler);
|
||||
|
@ -149,7 +149,7 @@ export type APIRequest =
|
||||
| ToolCallRequest;
|
||||
|
||||
export interface IStdioConnectionItem {
|
||||
type: 'STDIO';
|
||||
type: 'stdio';
|
||||
name: string;
|
||||
command: string;
|
||||
args: string[];
|
||||
@ -159,7 +159,7 @@ export interface IStdioConnectionItem {
|
||||
}
|
||||
|
||||
export interface ISSEConnectionItem {
|
||||
type: 'SSE';
|
||||
type: 'sse';
|
||||
name: string;
|
||||
url: string;
|
||||
oauth?: string;
|
||||
@ -169,13 +169,13 @@ export interface ISSEConnectionItem {
|
||||
|
||||
|
||||
export interface IStdioLaunchSignature {
|
||||
type: 'STDIO';
|
||||
type: 'stdio';
|
||||
commandString: string;
|
||||
cwd: string;
|
||||
}
|
||||
|
||||
export interface ISSELaunchSignature {
|
||||
type:'SSE';
|
||||
type:'sse';
|
||||
url: string;
|
||||
oauth: string;
|
||||
}
|
||||
|
@ -6,28 +6,28 @@ const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
name : "default",
|
||||
path : "/",
|
||||
redirect : baseURL + "debug"
|
||||
redirect : baseURL + "/debug"
|
||||
},
|
||||
{
|
||||
path: baseURL + "debug",
|
||||
path: baseURL + "/debug",
|
||||
name: "debug",
|
||||
component: () => import( /* webpackMode: "eager" */ "@/views/debug/index.vue"),
|
||||
meta: { title: "Debug" }
|
||||
},
|
||||
{
|
||||
path: baseURL + "connect",
|
||||
path: baseURL + "/connect",
|
||||
name: "connect",
|
||||
component: () => import( /* webpackMode: "eager" */ "@/views/connect/index.vue"),
|
||||
meta: { title: "Connect" }
|
||||
},
|
||||
{
|
||||
path: baseURL + "setting",
|
||||
path: baseURL + "/setting",
|
||||
name: "setting",
|
||||
component: () => import( /* webpackMode: "eager" */ "@/views/setting/index.vue"),
|
||||
meta: { title: "Setting" }
|
||||
},
|
||||
{
|
||||
path: baseURL + "about",
|
||||
path: baseURL + "/about",
|
||||
name: "about",
|
||||
component: () => import( /* webpackMode: "eager" */ "@/views/about/index.vue"),
|
||||
meta: { title: "Tools" }
|
||||
|
@ -6,7 +6,7 @@
|
||||
</span>
|
||||
|
||||
<p>
|
||||
OpenMCP Client 0.0.9 由 OpenMCP@<a href="https://www.zhihu.com/people/can-meng-zhong-de-che-xian">锦恢</a> 开发
|
||||
OpenMCP Client 0.0.8 由 OpenMCP@<a href="https://www.zhihu.com/people/can-meng-zhong-de-che-xian">锦恢</a> 开发
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<!-- STDIO 模式下的命令输入 -->
|
||||
<div class="connection-option" v-if="client.connectionArgs.type === 'STDIO'">
|
||||
<div class="connection-option" v-if="connectionMethods.current === 'STDIO'">
|
||||
<span>{{ t('connect-sigature') }}</span>
|
||||
<span style="width: 310px;">
|
||||
<el-form :model="client.connectionArgs" :rules="rules" ref="stdioForm">
|
||||
<el-form :model="connectionArgs" :rules="rules" ref="stdioForm">
|
||||
<el-form-item prop="commandString">
|
||||
<div class="input-with-label">
|
||||
<span class="input-label">{{ t("command") }}</span>
|
||||
<el-input v-model="client.connectionArgs.commandString" placeholder="mcp run <your script>"></el-input>
|
||||
<el-input v-model="connectionArgs.commandString" placeholder="mcp run <your script>"></el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="cwd">
|
||||
<div class="input-with-label">
|
||||
<span class="input-label">{{ t('cwd') }}</span>
|
||||
<el-input v-model="client.connectionArgs.cwd" placeholder="cwd, 可为空"></el-input>
|
||||
<el-input v-model="connectionArgs.cwd" placeholder="cwd, 可为空"></el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@ -24,17 +24,17 @@
|
||||
<div class="connection-option" v-else>
|
||||
<span>{{ t('connect-sigature') }}</span>
|
||||
<span style="width: 310px;">
|
||||
<el-form :model="client.connectionArgs" :rules="rules" ref="urlForm">
|
||||
<el-form-item prop="url">
|
||||
<el-form :model="connectionArgs" :rules="rules" ref="urlForm">
|
||||
<el-form-item prop="urlString">
|
||||
<div class="input-with-label">
|
||||
<span class="input-label">URL</span>
|
||||
<el-input v-model="client.connectionArgs.url" placeholder="http://"></el-input>
|
||||
<el-input v-model="connectionArgs.urlString" placeholder="http://"></el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="oauth">
|
||||
<div class="input-with-label">
|
||||
<span class="input-label">OAuth</span>
|
||||
<el-input v-model="client.connectionArgs.oauth" placeholder="认证签名, 可为空"></el-input>
|
||||
<el-input v-model="connectionArgs.oauth" placeholder="认证签名, 可为空"></el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@ -43,23 +43,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue';
|
||||
import { defineComponent, reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { mcpClientAdapter } from './core';
|
||||
import { connectionArgs, connectionMethods } from './connection';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const client = mcpClientAdapter.clients[props.index];
|
||||
|
||||
const stdioForm = ref<FormInstance>()
|
||||
const urlForm = ref<FormInstance>()
|
||||
|
||||
@ -74,7 +65,7 @@ const rules = reactive<FormRules>({
|
||||
oauth: [
|
||||
{ required: false, trigger: 'blur' }
|
||||
],
|
||||
url: [
|
||||
urlString: [
|
||||
{ required: true, message: 'URL不能为空', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
@ -82,7 +73,7 @@ const rules = reactive<FormRules>({
|
||||
// 验证当前活动表单
|
||||
const validateForm = async () => {
|
||||
try {
|
||||
if (client.connectionArgs.type === 'STDIO') {
|
||||
if (connectionMethods.current === 'STDIO') {
|
||||
await stdioForm.value?.validate()
|
||||
} else {
|
||||
await urlForm.value?.validate()
|
||||
|
@ -3,7 +3,7 @@
|
||||
<span>{{ t('log') }}</span>
|
||||
<el-scrollbar height="90%">
|
||||
<div class="output-content">
|
||||
<div v-for="(log, index) in client.connectionResult.logString" :key="index" :class="log.type">
|
||||
<div v-for="(log, index) in connectionResult.logString" :key="index" :class="log.type">
|
||||
<span class="log-message">{{ log.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -14,17 +14,9 @@
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { mcpClientAdapter } from './core';
|
||||
import { connectionResult } from './connection';
|
||||
|
||||
defineComponent({ name: 'connection-log' });
|
||||
const props = defineProps({
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const client = mcpClientAdapter.clients[props.index];
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
<div class="connection-option">
|
||||
<span>{{ t('connection-method') }}</span>
|
||||
<span style="width: 200px;">
|
||||
<el-select name="language-setting" class="language-setting" v-model="client.connectionArgs.type">
|
||||
<el-option v-for="option in connectionSelectDataViewOption" :value="option.value" :label="option.label"
|
||||
<el-select name="language-setting" class="language-setting" v-model="connectionMethods.current">
|
||||
<el-option v-for="option in connectionMethods.data" :value="option.value" :label="option.label"
|
||||
:key="option.label"></el-option>
|
||||
</el-select>
|
||||
</span>
|
||||
@ -13,17 +13,9 @@
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { connectionSelectDataViewOption, mcpClientAdapter } from './core';
|
||||
import { connectionMethods } from './connection';
|
||||
|
||||
defineComponent({ name: 'connection-method' });
|
||||
const props = defineProps({
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const client = mcpClientAdapter.clients[props.index];
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
|
@ -1,143 +0,0 @@
|
||||
<template>
|
||||
<el-scrollbar>
|
||||
<div class="connection-container">
|
||||
<div class="connect-panel-container"
|
||||
:ref="el => client.connectionSettingRef = el"
|
||||
>
|
||||
<ConnectionMethod :index="props.index" />
|
||||
<ConnectionArgs :index="props.index" />
|
||||
<ConnectionEnvironment :index="props.index" />
|
||||
|
||||
<div class="connect-action">
|
||||
<el-button type="primary" size="large" :loading="isLoading" :disabled="!client.connectionResult"
|
||||
@click="connect()">
|
||||
<span class="iconfont icon-connect" v-if="!isLoading"></span>
|
||||
{{ t('connect.appearance.connect') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="connect-panel-container"
|
||||
:ref="el => client.connectionLogRef = el"
|
||||
>
|
||||
<ConnectionLog :index="props.index" />
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import ConnectionMethod from './connection-method.vue';
|
||||
import ConnectionArgs from './connection-args.vue';
|
||||
import ConnectionEnvironment from './connection-environment.vue';
|
||||
import ConnectionLog from './connection-log.vue';
|
||||
|
||||
import { getPlatform } from '@/api/platform';
|
||||
import { mcpClientAdapter } from './core';
|
||||
|
||||
defineComponent({ name: 'connection-panel' });
|
||||
|
||||
const props = defineProps({
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const client = mcpClientAdapter.clients[props.index];
|
||||
|
||||
console.log(client);
|
||||
console.log(client.connectionSettingRef);
|
||||
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
||||
async function connect() {
|
||||
isLoading.value = true;
|
||||
|
||||
const platform = getPlatform();
|
||||
const ok = await client.connect();
|
||||
|
||||
if (ok) {
|
||||
mcpClientAdapter.saveLaunchSignature();
|
||||
}
|
||||
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.connection-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
||||
.connect-panel-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 45%;
|
||||
min-width: 300px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.connection-option {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--background);
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: .5em;
|
||||
border: 1px solid var(--background);
|
||||
}
|
||||
|
||||
.connection-option>span:first-child {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.input-env-container {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.display-env {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.input-env-container>span {
|
||||
width: 150px;
|
||||
margin-right: 10px;
|
||||
display: flex;
|
||||
height: 30px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.input-env-container .iconfont {
|
||||
font-size: 20px;
|
||||
border-radius: 99em;
|
||||
color: var(--foreground);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: var(--animation-3s);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.input-env-container .iconfont:hover {
|
||||
color: var(--main-color);
|
||||
transition: var(--animation-3s);
|
||||
}
|
||||
|
||||
.connect-action {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
383
renderer/src/views/connect/connection.ts
Normal file
383
renderer/src/views/connect/connection.ts
Normal file
@ -0,0 +1,383 @@
|
||||
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();
|
||||
}
|
@ -1,331 +0,0 @@
|
||||
import { useMessageBridge } from "@/api/message-bridge";
|
||||
import { reactive } from "vue";
|
||||
import type { IConnectionResult, ConnectionTypeOptionItem, IConnectionArgs, IConnectionEnvironment, McpOptions } from "./type";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { loadPanels } 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 class McpClient {
|
||||
// 连接入参
|
||||
public connectionArgs: IConnectionArgs;
|
||||
// 连接出参
|
||||
public connectionResult: IConnectionResult;
|
||||
|
||||
// 预设环境变量,初始化的时候会去获取它们
|
||||
public presetsEnvironment: string[] = ['HOME', 'LOGNAME', 'PATH', 'SHELL', 'TERM', 'USER'];
|
||||
// 环境变量
|
||||
public connectionEnvironment: IConnectionEnvironment;
|
||||
|
||||
// logger 面板的 ref
|
||||
public connectionLogRef: any = null;
|
||||
// setting 面板的 ref
|
||||
public connectionSettingRef: any = null;
|
||||
|
||||
constructor(
|
||||
public clientVersion: string = '0.0.1',
|
||||
public clientNamePrefix: string = 'openmcp.connect'
|
||||
) {
|
||||
// 连接入参
|
||||
this.connectionArgs = {
|
||||
type: 'STDIO',
|
||||
commandString: '',
|
||||
cwd: '',
|
||||
url: '',
|
||||
oauth: ''
|
||||
};
|
||||
|
||||
// 连接出参
|
||||
this.connectionResult = {
|
||||
success: false,
|
||||
status: 'disconnected',
|
||||
clientId: '',
|
||||
name: '',
|
||||
version: '',
|
||||
logString: []
|
||||
};
|
||||
|
||||
// 环境变量
|
||||
this.connectionEnvironment = {
|
||||
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 cwd = this.connectionArgs.cwd;
|
||||
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,
|
||||
cwd,
|
||||
oauth,
|
||||
clientName,
|
||||
clientVersion,
|
||||
env,
|
||||
serverInfo: {
|
||||
name: this.connectionResult.name,
|
||||
version: this.connectionResult.version
|
||||
}
|
||||
};
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
public async connect() {
|
||||
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 false;
|
||||
} else {
|
||||
this.connectionResult.logString.push({
|
||||
type: 'info',
|
||||
message: msg.info || ''
|
||||
})
|
||||
}
|
||||
|
||||
this.connectionResult.status = msg.status;
|
||||
this.connectionResult.clientId = msg.clientId;
|
||||
this.connectionResult.name = msg.name;
|
||||
this.connectionResult.version = msg.version;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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[] = [];
|
||||
public currentClientIndex: number = 0;
|
||||
|
||||
private defaultClient: McpClient = new McpClient();
|
||||
|
||||
constructor(
|
||||
public platform: string
|
||||
) { }
|
||||
|
||||
/**
|
||||
* @description 获取连接参数签名
|
||||
* @returns
|
||||
*/
|
||||
public async getLaunchSignature(): Promise<IConnectionArgs[]> {
|
||||
const bridge = useMessageBridge();
|
||||
const { code, msg } = await bridge.commandRequest(this.platform + '/launch-signature');
|
||||
|
||||
if (code !== 200) {
|
||||
const message = msg.toString();
|
||||
ElMessage.error(message);
|
||||
return [];
|
||||
}
|
||||
|
||||
// 判断一下版本,新版本的 msg 应该是数组,老版本是对象
|
||||
// 返回的数组的第一个为主节点,其余为从节点
|
||||
if (Array.isArray(msg)) {
|
||||
return msg;
|
||||
}
|
||||
return [msg];
|
||||
}
|
||||
|
||||
get masterNode() {
|
||||
if (this.clients.length === 0) {
|
||||
return this.defaultClient;
|
||||
}
|
||||
return this.clients[0];
|
||||
}
|
||||
|
||||
public async saveLaunchSignature() {
|
||||
const bridge = useMessageBridge();
|
||||
const options: McpOptions[] = this.clients.map(client => client.connectOption);
|
||||
|
||||
// 同步成功的连接参数到后端,更新 vscode treeview 中的列表
|
||||
const deserializeOption = JSON.parse(JSON.stringify(options));
|
||||
bridge.postMessage({
|
||||
command: platform + '/update-connection-signature',
|
||||
data: deserializeOption
|
||||
});
|
||||
}
|
||||
|
||||
public async launch() {
|
||||
const launchSignature = await this.getLaunchSignature();
|
||||
console.log('launchSignature', launchSignature);
|
||||
|
||||
let allOk = true;
|
||||
|
||||
for (const item of launchSignature) {
|
||||
|
||||
// 创建一个新的客户端
|
||||
const client = new McpClient();
|
||||
|
||||
// 同步连接参数
|
||||
await client.acquireConnectionSignature(item);
|
||||
|
||||
// 同步环境变量
|
||||
await client.handleEnvSwitch(true);
|
||||
|
||||
// 连接
|
||||
const ok = await client.connect(this.platform);
|
||||
allOk &&= ok;
|
||||
|
||||
this.clients.push(client);
|
||||
}
|
||||
|
||||
// 如果全部成功,保存连接参数
|
||||
if (allOk) {
|
||||
this.saveLaunchSignature();
|
||||
}
|
||||
}
|
||||
|
||||
public async loadPanels() {
|
||||
const masterNode = this.clients[0];
|
||||
await loadPanels(masterNode);
|
||||
}
|
||||
}
|
||||
|
||||
const platform = getPlatform();
|
||||
export const mcpClientAdapter = reactive(
|
||||
new McpClientAdapter(platform)
|
||||
);
|
@ -5,7 +5,7 @@
|
||||
|
||||
<el-switch
|
||||
v-model="envEnabled"
|
||||
@change="(enable: boolean) => client.handleEnvSwitch(enable)"
|
||||
@change="handleEnvSwitch"
|
||||
inline-prompt
|
||||
active-text="预设"
|
||||
inactive-text="预设"
|
||||
@ -14,10 +14,10 @@
|
||||
<div class="input-env">
|
||||
<span class="input-env-container">
|
||||
<span>
|
||||
<el-input v-model="client.connectionEnvironment.newKey" @keyup.enter="addEnvVar"></el-input>
|
||||
<el-input v-model="connectionEnv.newKey" @keyup.enter="addEnvVar"></el-input>
|
||||
</span>
|
||||
<span>
|
||||
<el-input v-model="client.connectionEnvironment.newValue" @keyup.enter="addEnvVar"></el-input>
|
||||
<el-input v-model="connectionEnv.newValue" @keyup.enter="addEnvVar"></el-input>
|
||||
</span>
|
||||
<span>
|
||||
<div @click="addEnvVar">
|
||||
@ -28,7 +28,7 @@
|
||||
</div>
|
||||
<el-scrollbar height="200px" width="350px" class="display-env-container">
|
||||
<div class="display-env">
|
||||
<div class="input-env-container" v-for="option of client.connectionEnvironment.data" :key="option.key">
|
||||
<div class="input-env-container" v-for="option of connectionEnv.data" :key="option.key">
|
||||
<span> <el-input v-model="option.key"></el-input></span>
|
||||
<span> <el-input v-model="option.value" show-password></el-input></span>
|
||||
<span @click="deleteEnvVar(option)">
|
||||
@ -44,18 +44,9 @@
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { mcpClientAdapter } from './core';
|
||||
import type { EnvItem } from './type';
|
||||
import { connectionEnv, type EnvItem, handleEnvSwitch } from './connection';
|
||||
|
||||
defineComponent({ name: 'env-var' });
|
||||
const props = defineProps({
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const client = mcpClientAdapter.clients[props.index];
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@ -64,24 +55,24 @@ const { t } = useI18n();
|
||||
*/
|
||||
function addEnvVar() {
|
||||
// 检查是否存在一样的 key
|
||||
const currentKey = client.connectionEnvironment.newKey;
|
||||
const currentValue = client.connectionEnvironment.newValue;
|
||||
const currentKey = connectionEnv.newKey;
|
||||
const currentValue = connectionEnv.newValue;
|
||||
|
||||
if (currentKey.length === 0 || currentValue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sameNameItems = client.connectionEnvironment.data.filter(item => item.key === currentKey);
|
||||
const sameNameItems = connectionEnv.data.filter(item => item.key === currentKey);
|
||||
|
||||
if (sameNameItems.length > 0) {
|
||||
const conflictItem = sameNameItems[0];
|
||||
conflictItem.value = currentValue;
|
||||
} else {
|
||||
client.connectionEnvironment.data.push({
|
||||
connectionEnv.data.push({
|
||||
key: currentKey, value: currentValue
|
||||
});
|
||||
client.connectionEnvironment.newKey = '';
|
||||
client.connectionEnvironment.newValue = '';
|
||||
connectionEnv.newKey = '';
|
||||
connectionEnv.newValue = '';
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,10 +81,11 @@ function addEnvVar() {
|
||||
*/
|
||||
function deleteEnvVar(option: EnvItem) {
|
||||
const currentKey = option.key;
|
||||
const reserveItems = client.connectionEnvironment.data.filter(item => item.key !== currentKey);
|
||||
client.connectionEnvironment.data = reserveItems;
|
||||
const reserveItems = connectionEnv.data.filter(item => item.key !== currentKey);
|
||||
connectionEnv.data = reserveItems;
|
||||
}
|
||||
|
||||
|
||||
const envEnabled = ref(true);
|
||||
|
||||
</script>
|
@ -1,30 +0,0 @@
|
||||
import { getTour, loadSetting } from "@/hook/setting";
|
||||
import { ElLoading } from "element-plus";
|
||||
import { pinkLog } from "../setting/util";
|
||||
import { mcpClientAdapter } from "./core";
|
||||
|
||||
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();
|
||||
}
|
@ -1,105 +1,128 @@
|
||||
<template>
|
||||
<div class="connection-container">
|
||||
<div class="server-list">
|
||||
<div v-for="(client, index) in mcpClientAdapter.clients" :key="index" class="server-item"
|
||||
:class="{ 'active': mcpClientAdapter.currentClientIndex === index }" @click="selectServer(index)">
|
||||
<span class="connect-status">
|
||||
<span v-if="client.connectionResult.success">
|
||||
<span class="iconfont icon-connect"></span>
|
||||
<span class="iconfont icon-dui"></span>
|
||||
</span>
|
||||
<span v-else>
|
||||
<span class="iconfont icon-connect"></span>
|
||||
<span class="server-name"> Unconnected </span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="add-server" @click="addServer">
|
||||
<span class="iconfont icon-add"></span>
|
||||
<el-scrollbar>
|
||||
<div class="connection-container">
|
||||
<div class="connect-panel-container"
|
||||
:ref="el => connectionSettingRef = el"
|
||||
>
|
||||
<ConnectionMethod></ConnectionMethod>
|
||||
<ConnectionArgs></ConnectionArgs>
|
||||
<EnvVar></EnvVar>
|
||||
|
||||
<div class="connect-action">
|
||||
<el-button type="primary" size="large" :loading="isLoading" :disabled="!connectionResult"
|
||||
@click="suitableConnect()">
|
||||
<span class="iconfont icon-connect" v-if="!isLoading"></span>
|
||||
{{ t('connect.appearance.connect') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-container">
|
||||
<ConnectionPanel v-if="mcpClientAdapter.clients.length > 0" :index="mcpClientAdapter.currentClientIndex" />
|
||||
|
||||
<div class="connect-panel-container"
|
||||
:ref="el => connectionLogRef = el"
|
||||
>
|
||||
<ConnectionLog></ConnectionLog>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import ConnectionPanel from './connection-panel.vue';
|
||||
import { McpClient, mcpClientAdapter } from './core';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
defineComponent({ name: 'connection' });
|
||||
const { t } = useI18n();
|
||||
|
||||
function selectServer(index: number) {
|
||||
mcpClientAdapter.currentClientIndex = index;
|
||||
import { connectionLogRef, connectionResult, connectionSettingRef, doConnect } from './connection';
|
||||
|
||||
import ConnectionMethod from './connection-method.vue';
|
||||
import ConnectionArgs from './connection-args.vue';
|
||||
import EnvVar from './env-var.vue';
|
||||
|
||||
import ConnectionLog from './connection-log.vue';
|
||||
import { getPlatform } from '@/api/platform';
|
||||
|
||||
defineComponent({ name: 'connect' });
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
||||
async function suitableConnect() {
|
||||
isLoading.value = true;
|
||||
|
||||
const plaform = getPlatform();
|
||||
|
||||
await doConnect({ namespace: plaform, updateCommandString: false })
|
||||
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
function addServer() {
|
||||
ElMessage.info('Add server is not implemented yet');
|
||||
mcpClientAdapter.clients.push(new McpClient());
|
||||
mcpClientAdapter.currentClientIndex = mcpClientAdapter.clients.length - 1;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.connection-container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.server-list {
|
||||
width: 150px;
|
||||
border-right: 1px solid var(--border-color);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.server-name {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.server-item {
|
||||
padding: 10px;
|
||||
margin-bottom: 5px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
.connect-panel-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
width: 45%;
|
||||
min-width: 300px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.connection-option {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--background);
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: .5em;
|
||||
border: 1px solid var(--background);
|
||||
}
|
||||
|
||||
.connection-option>span:first-child {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.input-env-container {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.display-env {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.input-env-container>span {
|
||||
width: 150px;
|
||||
margin-right: 10px;
|
||||
display: flex;
|
||||
height: 30px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.server-item.active {
|
||||
background-color: var(--main-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.server-status {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.server-status.connected {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.server-status.disconnected {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.add-server {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
.input-env-container .iconfont {
|
||||
font-size: 20px;
|
||||
border-radius: 99em;
|
||||
color: var(--foreground);
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
border: 1px dashed var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: var(--animation-3s);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.add-server:hover {
|
||||
background-color: var(--background);
|
||||
.input-env-container .iconfont:hover {
|
||||
color: var(--main-color);
|
||||
transition: var(--animation-3s);
|
||||
}
|
||||
|
||||
.panel-container {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
.connect-action {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
@ -1,81 +0,0 @@
|
||||
|
||||
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 {
|
||||
info?: string;
|
||||
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
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<!-- TODO: 支持更多的 server -->
|
||||
<span
|
||||
class="debug-option"
|
||||
:class="{ 'disable': !client.connectionResult.success }"
|
||||
:class="{ 'disable': !connectionResult.success }"
|
||||
v-for="(option, index) of debugOptions"
|
||||
:key="index"
|
||||
@click="chooseDebugMode(index)"
|
||||
@ -25,14 +25,13 @@
|
||||
import { debugModes, tabs } from '@/components/main-panel/panel';
|
||||
import { defineComponent, markRaw, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { connectionResult } from '../connect/connection';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { welcomeRef } from './welcome';
|
||||
import { mcpClientAdapter } from '../connect/core';
|
||||
|
||||
defineComponent({ name: 'welcome' });
|
||||
|
||||
const { t } = useI18n();
|
||||
const client = mcpClientAdapter.masterNode;
|
||||
|
||||
const debugOptions = [
|
||||
{
|
||||
@ -60,7 +59,7 @@ const debugOptions = [
|
||||
function chooseDebugMode(index: number) {
|
||||
|
||||
// TODO: 支持更多的 server
|
||||
if (client.connectionResult.success) {
|
||||
if (connectionResult.success) {
|
||||
const activeTab = tabs.activeTab;
|
||||
activeTab.component = markRaw(debugModes[index]);
|
||||
|
||||
|
@ -1,41 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "nodenext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"moduleResolution": "nodenext",
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"useDefineForClassFields": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"types": [
|
||||
"webpack-env"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"tests/**/*.ts",
|
||||
"tests/**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
@ -15,14 +15,5 @@ export default defineConfig(({ mode }) => ({
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
base: mode === 'website' ? '/mcp/' : '/',
|
||||
build: {
|
||||
cssCodeSplit: false, // 禁用 CSS 代码分割
|
||||
rollupOptions: {
|
||||
output: {
|
||||
inlineDynamicImports: true, // 将动态导入的内容内联
|
||||
manualChunks: undefined, // 禁用手动分块
|
||||
},
|
||||
},
|
||||
},
|
||||
base: mode === 'website' ? '/mcp/' : '/'
|
||||
}))
|
||||
|
@ -1,59 +0,0 @@
|
||||
const { TaskLoop } = require('../../openmcp-sdk/task-loop');
|
||||
const { TaskLoopAdapter } = require('../../openmcp-sdk/service');
|
||||
|
||||
async function main() {
|
||||
// 创建适配器,负责通信和 mcp 连接
|
||||
const adapter = new TaskLoopAdapter();
|
||||
|
||||
// 创建事件循环驱动器
|
||||
const taskLoop = new TaskLoop({ adapter });
|
||||
|
||||
// 配置改次事件循环使用的大模型
|
||||
taskLoop.setLlmConfig({
|
||||
id: 'deepseek',
|
||||
baseUrl: 'https://api.deepseek.com/v1',
|
||||
userToken: process.env['DEEPSEEK_API_TOKEN'],
|
||||
userModel: 'deepseek-chat'
|
||||
});
|
||||
|
||||
// 创建当前事件循环对应的上下文,并且配置当前上下文的设置
|
||||
const storage = {
|
||||
messages: [],
|
||||
settings: {
|
||||
temperature: 0.7,
|
||||
enableTools: [],
|
||||
systemPrompt: 'you are a clever bot',
|
||||
contextLength: 20
|
||||
}
|
||||
};
|
||||
|
||||
// 本次发出的问题
|
||||
const message = 'hello world';
|
||||
|
||||
// 事件循环结束的句柄
|
||||
taskLoop.registerOnDone(() => {
|
||||
console.log('taskLoop done');
|
||||
});
|
||||
|
||||
// 事件循环每一次 epoch 开始的句柄
|
||||
taskLoop.registerOnError((error) => {
|
||||
console.log('taskLoop error', error);
|
||||
});
|
||||
|
||||
// 事件循环出现 error 时的句柄(出现 error 不一定会停止事件循环)
|
||||
taskLoop.registerOnEpoch(() => {
|
||||
console.log('taskLoop epoch');
|
||||
});
|
||||
|
||||
// 开启事件循环
|
||||
await taskLoop.start(storage, message);
|
||||
|
||||
// 打印上下文,最终的回答在 messages.at(-1) 中
|
||||
console.log(storage.messages);
|
||||
|
||||
const content = storage.messages.at(-1).content;
|
||||
console.log(content);
|
||||
|
||||
}
|
||||
|
||||
main();
|
10
servers/uv.lock
generated
10
servers/uv.lock
generated
@ -175,10 +175,10 @@ wheels = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx-SSE"
|
||||
name = "httpx-sse"
|
||||
version = "0.4.0"
|
||||
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 = [
|
||||
{ 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 = [
|
||||
{ name = "anyio" },
|
||||
{ name = "httpx" },
|
||||
{ name = "httpx-SSE" },
|
||||
{ name = "httpx-sse" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "SSE-starlette" },
|
||||
{ name = "sse-starlette" },
|
||||
{ name = "starlette" },
|
||||
{ name = "uvicorn" },
|
||||
]
|
||||
@ -477,7 +477,7 @@ wheels = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "SSE-starlette"
|
||||
name = "sse-starlette"
|
||||
version = "2.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
|
5023
service/package-lock.json
generated
Normal file
5023
service/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,13 @@
|
||||
{
|
||||
"name": "@openmcp/service",
|
||||
"name": "openmcp-service",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
"dev": "ts-node-dev --respawn --transpile-only src/main.ts",
|
||||
"serve": "ts-node-dev --respawn --transpile-only src/main.ts",
|
||||
"build": "tsc",
|
||||
"build:watch": "tsc --watch",
|
||||
"postbuild": "node ./scripts/post-build.mjs",
|
||||
"start": "node dist/main.js",
|
||||
"start:prod": "NODE_ENV=production node dist/main.js",
|
||||
"debug": "node --inspect -r ts-node/register src/main.ts",
|
||||
|
@ -1,46 +0,0 @@
|
||||
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 })
|
||||
|
||||
const openMCPSdkPath = fsPath.join(currentDir, '..', 'openmcp-sdk');
|
||||
createDirIfExists(openMCPSdkPath);
|
||||
|
||||
const sdkRenderPath = fsPath.join(openMCPSdkPath, 'service');
|
||||
recreateDir(sdkRenderPath);
|
||||
|
||||
// 如果源目录不存在则报错
|
||||
if (!fs.existsSync(sourceDist)) {
|
||||
throw new Error(`Source directory not found: ${sourceDist}`)
|
||||
}
|
||||
|
||||
fs.cpSync(sourceDist, sdkRenderPath, { recursive: true })
|
@ -3,13 +3,9 @@ import { McpClient } from "../mcp/client.service";
|
||||
|
||||
export type RequestClientType = McpClient | undefined;
|
||||
|
||||
export interface RequestData {
|
||||
clientId?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export type RequestHandler<T, R> = (
|
||||
data: T & RequestData,
|
||||
client: RequestClientType,
|
||||
data: T,
|
||||
webview: PostMessageble
|
||||
) => Promise<R>;
|
||||
|
||||
|
@ -3,6 +3,7 @@ import type { PostMessageble } from "../hook/adapter";
|
||||
import { LlmController } from "../llm/llm.controller";
|
||||
import { ClientController } from "../mcp/client.controller";
|
||||
import { ConnectController } from "../mcp/connect.controller";
|
||||
import { client } from "../mcp/connect.service";
|
||||
import { OcrController } from "../mcp/ocr.controller";
|
||||
import { PanelController } from "../panel/panel.controller";
|
||||
import { SettingController } from "../setting/setting.controller";
|
||||
@ -23,7 +24,7 @@ export async function routeMessage(command: string, data: any, webview: PostMess
|
||||
|
||||
try {
|
||||
// TODO: select client based on something
|
||||
const res = await handler(data, webview);
|
||||
const res = await handler(client, data, webview);
|
||||
|
||||
// res.code = -1 代表当前请求不需要返回发送
|
||||
if (res.code >= 0) {
|
||||
|
@ -2,7 +2,7 @@ import { WebSocket } from 'ws';
|
||||
import { EventEmitter } from 'events';
|
||||
import { routeMessage } from '../common/router';
|
||||
import { McpOptions } from '../mcp/client.dto';
|
||||
import { clientMap, connectService } from '../mcp/connect.service';
|
||||
import { client, connectService } from '../mcp/connect.service';
|
||||
|
||||
// WebSocket 消息格式
|
||||
export interface WebSocketMessage {
|
||||
@ -114,12 +114,9 @@ export class TaskLoopAdapter {
|
||||
* @param mcpOption
|
||||
*/
|
||||
public async connectMcpServer(mcpOption: McpOptions) {
|
||||
const res = await connectService(mcpOption);
|
||||
const res = await connectService(undefined, mcpOption);
|
||||
if (res.code === 200) {
|
||||
console.log('✅ 成功连接 mcp 服务器: ' + res.msg);
|
||||
|
||||
const uuid = res.msg.uuid;
|
||||
const client = clientMap.get(uuid);
|
||||
const version = client?.getServerVersion();
|
||||
console.log(version);
|
||||
} else {
|
||||
@ -132,19 +129,14 @@ export class TaskLoopAdapter {
|
||||
* @returns
|
||||
*/
|
||||
public async listTools() {
|
||||
const tools = [];
|
||||
for (const client of clientMap.values()) {
|
||||
const clientTools = await client?.listTools();
|
||||
if (clientTools?.tools) {
|
||||
const enabledTools = clientTools.tools.map((tool) => {
|
||||
const enabledTools = {...tool, enabled: true };
|
||||
return enabledTools;
|
||||
});
|
||||
tools.push(...enabledTools);
|
||||
}
|
||||
const tools = await client?.listTools();
|
||||
if (tools?.tools) {
|
||||
return tools.tools.map((tool) => {
|
||||
const enabledTools = { ...tool, enabled: true };
|
||||
return enabledTools;
|
||||
});
|
||||
}
|
||||
|
||||
return tools;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,17 +1,13 @@
|
||||
import { Controller, RequestClientType } from "../common";
|
||||
import { RequestData } from "../common/index.dto";
|
||||
import { PostMessageble } from "../hook/adapter";
|
||||
import { getClient } from "../mcp/connect.service";
|
||||
import { abortMessageService, streamingChatCompletion } from "./llm.service";
|
||||
|
||||
export class LlmController {
|
||||
|
||||
@Controller('llm/chat/completions')
|
||||
async chatCompletion(data: RequestData, webview: PostMessageble) {
|
||||
async chatCompletion(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
let { tools = [] } = data;
|
||||
|
||||
const client = getClient(data.clientId);
|
||||
|
||||
if (tools.length > 0 && !client) {
|
||||
return {
|
||||
code: 501,
|
||||
@ -41,7 +37,7 @@ export class LlmController {
|
||||
}
|
||||
|
||||
@Controller('llm/chat/completions/abort')
|
||||
async abortChatCompletion(data: RequestData, webview: PostMessageble) {
|
||||
async abortChatCompletion(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
return abortMessageService(data, webview);
|
||||
}
|
||||
|
||||
|
@ -28,13 +28,13 @@ const logger = pino({
|
||||
export type MessageHandler = (message: VSCodeMessage) => void;
|
||||
|
||||
interface IStdioLaunchSignature {
|
||||
type: 'STDIO';
|
||||
type: 'stdio';
|
||||
commandString: string;
|
||||
cwd: string;
|
||||
}
|
||||
|
||||
interface ISSELaunchSignature {
|
||||
type:'SSE';
|
||||
type:'sse';
|
||||
url: string;
|
||||
oauth: string;
|
||||
}
|
||||
@ -42,20 +42,19 @@ interface ISSELaunchSignature {
|
||||
export type ILaunchSigature = IStdioLaunchSignature | ISSELaunchSignature;
|
||||
|
||||
function refreshConnectionOption(envPath: string) {
|
||||
const serverPath = path.join(__dirname, '..', '..', 'servers');
|
||||
|
||||
const defaultOption = {
|
||||
type:'STDIO',
|
||||
commandString: 'mcp run main.py',
|
||||
cwd: serverPath
|
||||
type:'stdio',
|
||||
command: 'mcp',
|
||||
args: ['run', 'main.py'],
|
||||
cwd: '../server'
|
||||
};
|
||||
|
||||
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4));
|
||||
|
||||
return { data: [ defaultOption ] };
|
||||
return defaultOption;
|
||||
}
|
||||
|
||||
function acquireConnectionOption() {
|
||||
function getInitConnectionOption() {
|
||||
const envPath = path.join(__dirname, '..', '.env');
|
||||
|
||||
if (!fs.existsSync(envPath)) {
|
||||
@ -64,15 +63,6 @@ function acquireConnectionOption() {
|
||||
|
||||
try {
|
||||
const option = JSON.parse(fs.readFileSync(envPath, 'utf-8'));
|
||||
|
||||
if (!option.data) {
|
||||
return refreshConnectionOption(envPath);
|
||||
}
|
||||
|
||||
if (option.data && option.data.length === 0) {
|
||||
return refreshConnectionOption(envPath);
|
||||
}
|
||||
|
||||
return option;
|
||||
|
||||
} catch (error) {
|
||||
@ -83,10 +73,26 @@ function acquireConnectionOption() {
|
||||
|
||||
function updateConnectionOption(data: any) {
|
||||
const envPath = path.join(__dirname, '..', '.env');
|
||||
const connection = { data };
|
||||
fs.writeFileSync(envPath, JSON.stringify(connection, null, 4));
|
||||
}
|
||||
|
||||
if (data.connectionType === 'STDIO') {
|
||||
const connectionItem = {
|
||||
type: 'stdio',
|
||||
command: data.command,
|
||||
args: data.args,
|
||||
cwd: data.cwd.replace(/\\/g, '/')
|
||||
};
|
||||
|
||||
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
|
||||
} else {
|
||||
const connectionItem = {
|
||||
type: 'sse',
|
||||
url: data.url,
|
||||
oauth: data.oauth
|
||||
};
|
||||
|
||||
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
|
||||
}
|
||||
}
|
||||
|
||||
const devHome = path.join(__dirname, '..', '..');
|
||||
setRunningCWD(devHome);
|
||||
@ -109,18 +115,30 @@ wss.on('connection', (ws: any) => {
|
||||
}
|
||||
});
|
||||
|
||||
const option = acquireConnectionOption();
|
||||
const option = getInitConnectionOption();
|
||||
|
||||
// 注册消息接受的管线
|
||||
webview.onDidReceiveMessage(message => {
|
||||
logger.info(`command: [${message.command || 'No Command'}]`);
|
||||
logger.info(`command: [${message.command || 'No Command'}]`);
|
||||
const { command, data } = message;
|
||||
|
||||
switch (command) {
|
||||
case 'web/launch-signature':
|
||||
const launchResultMessage: ILaunchSigature = option.type === 'stdio' ?
|
||||
{
|
||||
type: 'stdio',
|
||||
commandString: option.command + ' ' + option.args.join(' '),
|
||||
cwd: option.cwd || ''
|
||||
} :
|
||||
{
|
||||
type: 'sse',
|
||||
url: option.url,
|
||||
oauth: option.oauth || ''
|
||||
};
|
||||
|
||||
const launchResult = {
|
||||
code: 200,
|
||||
msg: option.data
|
||||
msg: launchResultMessage
|
||||
};
|
||||
|
||||
webview.postMessage({
|
||||
@ -130,7 +148,7 @@ wss.on('connection', (ws: any) => {
|
||||
|
||||
break;
|
||||
|
||||
case 'web/update-connection-signature':
|
||||
case 'web/update-connection-sigature':
|
||||
updateConnectionOption(data);
|
||||
break;
|
||||
|
||||
|
@ -1,14 +1,11 @@
|
||||
import { Controller } from "../common";
|
||||
import { RequestData } from "../common/index.dto";
|
||||
import { Controller, RequestClientType } from "../common";
|
||||
import { PostMessageble } from "../hook/adapter";
|
||||
import { postProcessMcpToolcallResponse } from "./client.service";
|
||||
import { getClient } from "./connect.service";
|
||||
|
||||
export class ClientController {
|
||||
|
||||
@Controller('server/version')
|
||||
async getServerVersion(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async getServerVersion(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
if (!client) {
|
||||
return {
|
||||
code: 501,
|
||||
@ -24,8 +21,7 @@ export class ClientController {
|
||||
}
|
||||
|
||||
@Controller('prompts/list')
|
||||
async listPrompts(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async listPrompts(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
if (!client) {
|
||||
const connectResult = {
|
||||
code: 501,
|
||||
@ -43,8 +39,7 @@ export class ClientController {
|
||||
}
|
||||
|
||||
@Controller('prompts/get')
|
||||
async getPrompt(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async getPrompt(client: RequestClientType, option: any, webview: PostMessageble) {
|
||||
if (!client) {
|
||||
return {
|
||||
code: 501,
|
||||
@ -52,7 +47,7 @@ export class ClientController {
|
||||
};
|
||||
}
|
||||
|
||||
const prompt = await client.getPrompt(data.promptId, data.args || {});
|
||||
const prompt = await client.getPrompt(option.promptId, option.args || {});
|
||||
return {
|
||||
code: 200,
|
||||
msg: prompt
|
||||
@ -60,8 +55,7 @@ export class ClientController {
|
||||
}
|
||||
|
||||
@Controller('resources/list')
|
||||
async listResources(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async listResources(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
if (!client) {
|
||||
return {
|
||||
code: 501,
|
||||
@ -77,8 +71,8 @@ export class ClientController {
|
||||
}
|
||||
|
||||
@Controller('resources/templates/list')
|
||||
async listResourceTemplates(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async listResourceTemplates(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
|
||||
if (!client) {
|
||||
return {
|
||||
code: 501,
|
||||
@ -94,8 +88,7 @@ export class ClientController {
|
||||
}
|
||||
|
||||
@Controller('resources/read')
|
||||
async readResource(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async readResource(client: RequestClientType, option: any, webview: PostMessageble) {
|
||||
if (!client) {
|
||||
return {
|
||||
code: 501,
|
||||
@ -103,7 +96,7 @@ export class ClientController {
|
||||
};
|
||||
}
|
||||
|
||||
const resource = await client.readResource(data.resourceUri);
|
||||
const resource = await client.readResource(option.resourceUri);
|
||||
console.log(resource);
|
||||
|
||||
return {
|
||||
@ -113,8 +106,7 @@ export class ClientController {
|
||||
}
|
||||
|
||||
@Controller('tools/list')
|
||||
async listTools(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async listTools(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
if (!client) {
|
||||
return {
|
||||
code: 501,
|
||||
@ -130,8 +122,7 @@ export class ClientController {
|
||||
}
|
||||
|
||||
@Controller('tools/call')
|
||||
async callTool(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async callTool(client: RequestClientType, option: any, webview: PostMessageble) {
|
||||
if (!client) {
|
||||
return {
|
||||
code: 501,
|
||||
@ -140,13 +131,18 @@ export class ClientController {
|
||||
}
|
||||
|
||||
const toolResult = await client.callTool({
|
||||
name: data.toolName,
|
||||
arguments: data.toolArgs,
|
||||
callToolOption: data.callToolOption
|
||||
name: option.toolName,
|
||||
arguments: option.toolArgs,
|
||||
callToolOption: option.callToolOption
|
||||
});
|
||||
|
||||
// console.log(JSON.stringify(toolResult, null, 2));
|
||||
|
||||
postProcessMcpToolcallResponse(toolResult, webview);
|
||||
|
||||
// console.log(JSON.stringify(toolResult, null, 2));
|
||||
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
msg: toolResult
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/STDIO.js";
|
||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/SSE.js";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||
import { Implementation } from "@modelcontextprotocol/sdk/types";
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/STDIO.js";
|
||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/SSE.js";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||
import type { McpOptions, McpTransport, IServerVersion, ToolCallResponse, ToolCallContent } from './client.dto';
|
||||
import { PostMessageble } from "../hook/adapter";
|
||||
|
@ -1,19 +1,17 @@
|
||||
import { Controller } from '../common';
|
||||
import { Controller, RequestClientType } from '../common';
|
||||
import { PostMessageble } from '../hook/adapter';
|
||||
import { RequestData } from '../common/index.dto';
|
||||
import { connectService, getClient } from './connect.service';
|
||||
import { connectService } from './connect.service';
|
||||
|
||||
export class ConnectController {
|
||||
|
||||
@Controller('connect')
|
||||
async connect(data: any, webview: PostMessageble) {
|
||||
const res = await connectService(data);
|
||||
async connect(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
const res = await connectService(client, data);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Controller('lookup-env-var')
|
||||
async lookupEnvVar(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async lookupEnvVar(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
const { keys } = data;
|
||||
const values = keys.map((key: string) => process.env[key] || '');
|
||||
|
||||
@ -24,8 +22,7 @@ export class ConnectController {
|
||||
}
|
||||
|
||||
@Controller('ping')
|
||||
async ping(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async ping(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
if (!client) {
|
||||
const connectResult = {
|
||||
code: 501,
|
||||
|
@ -1,180 +1,69 @@
|
||||
import { execSync, spawnSync } from 'node:child_process';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { RequestClientType } from '../common';
|
||||
import { connect } from './client.service';
|
||||
import { RestfulResponse } from '../common/index.dto';
|
||||
import { McpOptions } from './client.dto';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
|
||||
export const clientMap: Map<string, RequestClientType> = new Map();
|
||||
export function getClient(clientId?: string): RequestClientType | undefined {
|
||||
return clientMap.get(clientId || '');
|
||||
}
|
||||
|
||||
// TODO: 更多的 client
|
||||
export let client: RequestClientType = undefined;
|
||||
|
||||
export function tryGetRunCommandError(command: string, args: string[] = [], cwd?: string): string | null {
|
||||
try {
|
||||
try {
|
||||
console.log('current command', command);
|
||||
console.log('current args', args);
|
||||
|
||||
const result = spawnSync(command, args, {
|
||||
cwd: cwd || process.cwd(),
|
||||
stdio: 'pipe',
|
||||
encoding: 'utf-8'
|
||||
});
|
||||
|
||||
const commandString = [command, ...args].join(' ');
|
||||
|
||||
const result = execSync(commandString, {
|
||||
cwd: cwd || process.cwd()
|
||||
}).toString('utf-8');
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
if (result.error) {
|
||||
return result.error.message;
|
||||
}
|
||||
if (result.status !== 0) {
|
||||
return result.stderr || `Command failed with code ${result.status}`;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
}
|
||||
|
||||
function getCWD(option: McpOptions) {
|
||||
if (option.cwd) {
|
||||
return option.cwd;
|
||||
}
|
||||
const file = option.args?.at(-1);
|
||||
if (file) {
|
||||
return path.dirname(file);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getCommandFileExt(option: McpOptions) {
|
||||
const file = option.args?.at(-1);
|
||||
if (file) {
|
||||
return path.extname(file);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
function preprocessCommand(option: McpOptions): [McpOptions, string] {
|
||||
// 对于特殊表示的路径,进行特殊的支持
|
||||
if (option.args) {
|
||||
option.args = option.args.map(arg => {
|
||||
if (arg.startsWith('~/')) {
|
||||
return arg.replace('~', process.env.HOME || '');
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
}
|
||||
|
||||
if (option.connectionType === 'SSE' || option.connectionType === 'STREAMABLE_HTTP') {
|
||||
return [option, ''];
|
||||
}
|
||||
|
||||
const cwd = getCWD(option);
|
||||
if (!cwd) {
|
||||
return [option, ''];
|
||||
}
|
||||
|
||||
const ext = getCommandFileExt(option);
|
||||
if (!ext) {
|
||||
return [option, ''];
|
||||
}
|
||||
|
||||
// STDIO 模式下,对不同类型的项目进行额外支持
|
||||
// uv:如果没有初始化,则进行 uv sync,将 mcp 设置为虚拟环境的
|
||||
// npm:如果没有初始化,则进行 npm init,将 mcp 设置为虚拟环境
|
||||
// go:如果没有初始化,则进行 go mod init
|
||||
|
||||
let info: string = '';
|
||||
|
||||
switch (ext) {
|
||||
case '.py':
|
||||
info = initUv(cwd);
|
||||
break;
|
||||
case '.js':
|
||||
case '.ts':
|
||||
info = initNpm(cwd);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return [option, info];
|
||||
}
|
||||
|
||||
function initUv(cwd: string) {
|
||||
let projectDir = cwd;
|
||||
|
||||
while (projectDir!== path.dirname(projectDir)) {
|
||||
if (fs.readFileSync(projectDir).includes('pyproject.toml')) {
|
||||
break;
|
||||
}
|
||||
projectDir = path.dirname(projectDir);
|
||||
}
|
||||
|
||||
console.log(projectDir);
|
||||
|
||||
|
||||
const venv = path.join(projectDir, '.venv');
|
||||
const mcpCli = path.join(venv, 'bin', 'mcp');
|
||||
if (fs.existsSync(mcpCli)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let info = '';
|
||||
info += execSync('uv sync', { cwd: projectDir }).toString('utf-8') + '\n';
|
||||
info += execSync('uv add mcp "mcp[cli]"', { cwd: projectDir }).toString('utf-8') + '\n';
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
function initNpm(cwd: string) {
|
||||
let projectDir = cwd;
|
||||
|
||||
while (projectDir !== path.dirname(projectDir)) {
|
||||
if (fs.readFileSync(projectDir).includes('package.json')) {
|
||||
break;
|
||||
}
|
||||
projectDir = path.dirname(projectDir);
|
||||
}
|
||||
|
||||
const nodeModulesPath = path.join(projectDir, 'node_modules');
|
||||
if (fs.existsSync(nodeModulesPath)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return execSync('npm i', { cwd: projectDir }).toString('utf-8') + '\n';
|
||||
}
|
||||
|
||||
|
||||
export async function connectService(
|
||||
option: McpOptions
|
||||
_client: RequestClientType,
|
||||
option: McpOptions
|
||||
): Promise<RestfulResponse> {
|
||||
try {
|
||||
console.log('ready to connect', option);
|
||||
|
||||
const info = preprocessCommand(option);
|
||||
|
||||
const client = await connect(option);
|
||||
const uuid = randomUUID();
|
||||
clientMap.set(uuid, client);
|
||||
|
||||
const versionInfo = client.getServerVersion();
|
||||
|
||||
// 对于特殊表示的路径,进行特殊的支持
|
||||
if (option.args) {
|
||||
option.args = option.args.map(arg => {
|
||||
if (arg.startsWith('~/')) {
|
||||
return arg.replace('~', process.env.HOME || '');
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
}
|
||||
|
||||
client = await connect(option);
|
||||
const connectResult = {
|
||||
code: 200,
|
||||
msg: {
|
||||
status: 'success',
|
||||
clientId: uuid,
|
||||
name: versionInfo?.name,
|
||||
version: versionInfo?.version,
|
||||
info
|
||||
}
|
||||
msg: 'Connect to OpenMCP successfully\nWelcome back, Kirigaya'
|
||||
};
|
||||
|
||||
return connectResult;
|
||||
|
||||
return connectResult;
|
||||
} catch (error) {
|
||||
|
||||
console.log('meet error');
|
||||
console.log(error);
|
||||
|
||||
|
||||
// TODO: 这边获取到的 error 不够精致,如何才能获取到更加精准的错误
|
||||
// 比如 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)
|
||||
|
||||
let errorMsg = '';
|
||||
|
||||
@ -183,12 +72,12 @@ export async function connectService(
|
||||
}
|
||||
|
||||
errorMsg += (error as any).toString();
|
||||
|
||||
|
||||
const connectResult = {
|
||||
code: 500,
|
||||
msg: errorMsg
|
||||
};
|
||||
|
||||
return connectResult;
|
||||
return connectResult;
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,13 @@
|
||||
import { Controller } from "../common";
|
||||
import { Controller, RequestClientType } from "../common";
|
||||
import { PostMessageble } from "../hook/adapter";
|
||||
import { RequestData } from "../common/index.dto";
|
||||
import { getClient } from "../mcp/connect.service";
|
||||
import { systemPromptDB } from "../hook/db";
|
||||
import { loadTabSaveConfig, saveTabSaveConfig } from "./panel.service";
|
||||
|
||||
export class PanelController {
|
||||
@Controller('panel/save')
|
||||
async savePanel(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async savePanel(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
const serverInfo = client?.getServerVersion();
|
||||
saveTabSaveConfig(serverInfo, data);
|
||||
saveTabSaveConfig(serverInfo, data);
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
@ -18,11 +15,11 @@ export class PanelController {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Controller('panel/load')
|
||||
async loadPanel(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async loadPanel(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
const serverInfo = client?.getServerVersion();
|
||||
const config = loadTabSaveConfig(serverInfo);
|
||||
const config = loadTabSaveConfig(serverInfo);
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
@ -31,8 +28,7 @@ export class PanelController {
|
||||
}
|
||||
|
||||
@Controller('system-prompts/set')
|
||||
async setSystemPrompt(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async setSystemPrompt(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
const { name, content } = data;
|
||||
|
||||
await systemPromptDB.insert({
|
||||
@ -48,8 +44,7 @@ export class PanelController {
|
||||
}
|
||||
|
||||
@Controller('system-prompts/delete')
|
||||
async deleteSystemPrompt(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async deleteSystemPrompt(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
const { name } = data;
|
||||
await systemPromptDB.delete(name);
|
||||
return {
|
||||
@ -59,8 +54,7 @@ export class PanelController {
|
||||
}
|
||||
|
||||
@Controller('system-prompts/save')
|
||||
async saveSystemPrompts(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async saveSystemPrompts(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
const { prompts } = data;
|
||||
|
||||
await Promise.all(prompts.map((prompt: any) => {
|
||||
@ -78,8 +72,8 @@ export class PanelController {
|
||||
}
|
||||
|
||||
@Controller('system-prompts/load')
|
||||
async loadSystemPrompts(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async loadSystemPrompts(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
|
||||
const queryPrompts = await systemPromptDB.findAll();
|
||||
const prompts = [];
|
||||
for (const prompt of queryPrompts) {
|
||||
|
@ -29,13 +29,13 @@ const logger = pino({
|
||||
export type MessageHandler = (message: VSCodeMessage) => void;
|
||||
|
||||
interface IStdioLaunchSignature {
|
||||
type: 'STDIO';
|
||||
type: 'stdio';
|
||||
commandString: string;
|
||||
cwd: string;
|
||||
}
|
||||
|
||||
interface ISSELaunchSignature {
|
||||
type: 'SSE';
|
||||
type: 'sse';
|
||||
url: string;
|
||||
oauth: string;
|
||||
}
|
||||
@ -43,20 +43,19 @@ interface ISSELaunchSignature {
|
||||
export type ILaunchSigature = IStdioLaunchSignature | ISSELaunchSignature;
|
||||
|
||||
function refreshConnectionOption(envPath: string) {
|
||||
const serverPath = path.join(__dirname, '..', '..', 'servers');
|
||||
|
||||
const defaultOption = {
|
||||
type:'STDIO',
|
||||
commandString: 'mcp run main.py',
|
||||
cwd: serverPath
|
||||
type: 'stdio',
|
||||
command: 'mcp',
|
||||
args: ['run', 'main.py'],
|
||||
cwd: '../server'
|
||||
};
|
||||
|
||||
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4));
|
||||
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4));
|
||||
|
||||
return { data: [ defaultOption ] };
|
||||
return defaultOption;
|
||||
}
|
||||
|
||||
function acquireConnectionOption() {
|
||||
function getInitConnectionOption() {
|
||||
const envPath = path.join(__dirname, '..', '.env');
|
||||
|
||||
if (!fs.existsSync(envPath)) {
|
||||
@ -65,15 +64,6 @@ function acquireConnectionOption() {
|
||||
|
||||
try {
|
||||
const option = JSON.parse(fs.readFileSync(envPath, 'utf-8'));
|
||||
|
||||
if (!option.data) {
|
||||
return refreshConnectionOption(envPath);
|
||||
}
|
||||
|
||||
if (option.data && option.data.length === 0) {
|
||||
return refreshConnectionOption(envPath);
|
||||
}
|
||||
|
||||
return option;
|
||||
|
||||
} catch (error) {
|
||||
@ -91,8 +81,25 @@ const authPassword = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '.env
|
||||
|
||||
function updateConnectionOption(data: any) {
|
||||
const envPath = path.join(__dirname, '..', '.env');
|
||||
const connection = { data };
|
||||
fs.writeFileSync(envPath, JSON.stringify(connection, null, 4));
|
||||
|
||||
if (data.connectionType === 'STDIO') {
|
||||
const connectionItem = {
|
||||
type: 'stdio',
|
||||
command: data.command,
|
||||
args: data.args,
|
||||
cwd: data.cwd.replace(/\\/g, '/')
|
||||
};
|
||||
|
||||
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
|
||||
} else {
|
||||
const connectionItem = {
|
||||
type: 'sse',
|
||||
url: data.url,
|
||||
oauth: data.oauth
|
||||
};
|
||||
|
||||
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
|
||||
}
|
||||
}
|
||||
|
||||
const devHome = path.join(__dirname, '..', '..');
|
||||
@ -139,7 +146,7 @@ wss.on('connection', (ws: any) => {
|
||||
}
|
||||
});
|
||||
|
||||
const option = acquireConnectionOption();
|
||||
const option = getInitConnectionOption();
|
||||
|
||||
// 注册消息接受的管线
|
||||
webview.onDidReceiveMessage(message => {
|
||||
@ -148,9 +155,21 @@ wss.on('connection', (ws: any) => {
|
||||
|
||||
switch (command) {
|
||||
case 'web/launch-signature':
|
||||
const launchResultMessage: ILaunchSigature = option.type === 'stdio' ?
|
||||
{
|
||||
type: 'stdio',
|
||||
commandString: option.command + ' ' + option.args.join(' '),
|
||||
cwd: option.cwd || ''
|
||||
} :
|
||||
{
|
||||
type: 'sse',
|
||||
url: option.url,
|
||||
oauth: option.oauth || ''
|
||||
};
|
||||
|
||||
const launchResult = {
|
||||
code: 200,
|
||||
msg: option.data
|
||||
msg: launchResultMessage
|
||||
};
|
||||
|
||||
webview.postMessage({
|
||||
@ -160,7 +179,7 @@ wss.on('connection', (ws: any) => {
|
||||
|
||||
break;
|
||||
|
||||
case 'web/update-connection-signature':
|
||||
case 'web/update-connection-sigature':
|
||||
updateConnectionOption(data);
|
||||
break;
|
||||
|
||||
|
@ -1,14 +1,11 @@
|
||||
import { Controller } from "../common";
|
||||
import { Controller, RequestClientType } from "../common";
|
||||
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";
|
||||
|
||||
export class SettingController {
|
||||
|
||||
@Controller('setting/save')
|
||||
async saveSetting(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async saveSetting(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
saveSetting(data);
|
||||
console.log('Settings saved successfully');
|
||||
|
||||
@ -19,8 +16,8 @@ export class SettingController {
|
||||
}
|
||||
|
||||
@Controller('setting/load')
|
||||
async loadSetting(data: RequestData, webview: PostMessageble) {
|
||||
const client = getClient(data.clientId);
|
||||
async loadSetting(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
|
||||
const config = loadSetting();
|
||||
return {
|
||||
code: 200,
|
||||
@ -29,8 +26,10 @@ export class SettingController {
|
||||
}
|
||||
|
||||
@Controller('setting/set-tour')
|
||||
async setTourController(data: any, webview: PostMessageble) {
|
||||
async setTourController(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
|
||||
const { userHasReadGuide } = data;
|
||||
|
||||
setTour(userHasReadGuide);
|
||||
|
||||
return {
|
||||
@ -40,7 +39,7 @@ export class SettingController {
|
||||
}
|
||||
|
||||
@Controller('setting/get-tour')
|
||||
async getTourController(data: any, webview: PostMessageble) {
|
||||
async getTourController(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||
|
||||
const { userHasReadGuide } = getTour();
|
||||
|
||||
|
@ -10,20 +10,122 @@
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "hello",
|
||||
"content": "https://challenge.isic-archive.com/data/#2020",
|
||||
"extraInfo": {
|
||||
"created": 1747658023940,
|
||||
"created": 1747382016210,
|
||||
"state": "success",
|
||||
"serverName": "Huoshan DeepSeek"
|
||||
"serverName": "deepseek"
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Hello! How can I assist you today?",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_0_9dedd2c3-32ba-4f8c-993f-bb381b9845b0",
|
||||
"index": 0,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "k_navigate",
|
||||
"arguments": "{\"url\":\"https://challenge.isic-archive.com/data/#2020\"}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"extraInfo": {
|
||||
"created": 1747658024544,
|
||||
"created": 1747382021478,
|
||||
"state": "success",
|
||||
"serverName": "Huoshan DeepSeek"
|
||||
"serverName": "deepseek"
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "tool",
|
||||
"index": 0,
|
||||
"tool_call_id": "call_0_9dedd2c3-32ba-4f8c-993f-bb381b9845b0",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Navigated to https://challenge.isic-archive.com/data/#2020"
|
||||
}
|
||||
],
|
||||
"extraInfo": {
|
||||
"created": 1747382026814,
|
||||
"state": "success",
|
||||
"serverName": "deepseek",
|
||||
"usage": {
|
||||
"prompt_tokens": 1422,
|
||||
"completion_tokens": 31,
|
||||
"total_tokens": 1453,
|
||||
"prompt_tokens_details": {
|
||||
"cached_tokens": 0
|
||||
},
|
||||
"prompt_cache_hit_tokens": 0,
|
||||
"prompt_cache_miss_tokens": 1422
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_0_b4cd47c1-ad08-46a1-9a0e-d202dd2aef0a",
|
||||
"index": 0,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "k_get_full_page_text",
|
||||
"arguments": "{}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"extraInfo": {
|
||||
"created": 1747382031777,
|
||||
"state": "success",
|
||||
"serverName": "deepseek"
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "tool",
|
||||
"index": 0,
|
||||
"tool_call_id": "call_0_b4cd47c1-ad08-46a1-9a0e-d202dd2aef0a",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Execution result:\n\"mail_outline\\nSUBSCRIBE\\nLOGIN\\nSIGN UP\\nISIC Challenge Datasets\\n2016\\n2017\\n2018\\n2019\\n2020\\nTraining Data\\tTraining Ground Truth\\tTest Data\\tTest Ground Truth\\tLicense\\n\\n\\nDownload DICOM (48.9GB)\\n33,126 DICOM images with embedded metadata.\\n\\nDownload DICOM Corrected* (23.0GB)\\n33,126 DICOM images with embedded metadata.\\n\\nDownload JPEG (23GB)\\n33,126 JPEG images.\\n\\nDownload metadata (2MB)\\n33,126 metadata entries of patient ID, sex, age, and general anatomic site.\\n\\nDownload metadata v2 (2MB)\\n33,126 metadata entries of patient ID, lesion ID, sex, age, and general anatomic site.\\n\\nDownload duplicate image list (2MB)\\nList of 425 duplicate images.\\n\\n\\tDownload (2MB)\\n33,126 entries of gold standard lesion diagnoses.\\t\\n\\nDownload DICOM (15.3GB)\\n10,982 DICOM images with embedded metadata.\\n\\nDownload DICOM Corrected* (6.7GB)\\n10,982 DICOM images with embedded metadata.\\n\\nDownload JPEG (6.7GB)\\n10,982 JPEG images.\\n\\nDownload metadata (458KB)\\n10,982 metadata entries of patient ID, sex, age, and general anatomic site.\\n\\n\\t\\nNot Available\\nhelp_outline\\n\\tCC-BY-NC\\n\\n*The newer version of the DICOM files are provided to avoid potential errors stemming from readers implementing a strict DICOM verification, as implemented in http://dclunie.com/dicom3tools/dciodvfy.html.\\n\\nCiting 2020 datasets:\\nTo comply with the attribution requirements of the CC-BY-NC license , the aggregate \\\"ISIC 2020\\\" data must be cited as:\\n\\nInternational Skin Imaging Collaboration. SIIM-ISIC 2020 Challenge Dataset. International Skin Imaging Collaboration https://doi.org/10.34970/2020-ds01 (2020).\\n\\nCreative Commons Attribution-Non Commercial 4.0 International License.\\n\\nThe dataset was generated by the International Skin Imaging Collaboration (ISIC) and images are from the following sources: Hospital Clínic de Barcelona, Medical University of Vienna, Memorial Sloan Kettering Cancer Center, Melanoma Institute Australia, The University of Queensland, and the University of Athens Medical School.\\n\\nYou should have received a copy of the license along with this work.\\n\\nIf not, see https://creativecommons.org/licenses/by-nc/4.0/legalcode.txt .\\n\\nWhen referencing this dataset in your own manuscripts and publications, please use the following full citation. Please note this is a preprint and has not undergone peer review. It is being prepared for submission and if accepted to a peer reviewed journal the below will be updated accordingly:\\n\\n[1] Rotemberg, V., Kurtansky, N., Betz-Stablein, B., Caffery, L., Chousakos, E., Codella, N., Combalia, M., Dusza, S., Guitera, P., Gutman, D., Halpern, A., Helba, B., Kittler, H., Kose, K., Langer, S., Lioprys, K., Malvehy, J., Musthaq, S., Nanda, J., Reiter, O., Shih, G., Stratigos, A., Tschandl, P., Weber, J. & Soyer, P. A patient-centric dataset of images and metadata for identifying melanomas using clinical context. Sci Data 8, 34 (2021). https://doi.org/10.1038/s41597-021-00815-z\\n\\nTerms of Use|Contact\\nREPORT AN ISSUE\"\n\nConsole output:\n"
|
||||
}
|
||||
],
|
||||
"extraInfo": {
|
||||
"created": 1747382031793,
|
||||
"state": "success",
|
||||
"serverName": "deepseek",
|
||||
"usage": {
|
||||
"prompt_tokens": 1484,
|
||||
"completion_tokens": 17,
|
||||
"total_tokens": 1501,
|
||||
"prompt_tokens_details": {
|
||||
"cached_tokens": 1408
|
||||
},
|
||||
"prompt_cache_hit_tokens": 1408,
|
||||
"prompt_cache_miss_tokens": 76
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "### ISIC 2020 皮肤影像数据集 \n该数据集由国际皮肤影像协作组(ISIC)提供,用于训练和测试AI模型以识别皮肤病变,尤其是黑色素瘤。数据集包含大量皮肤镜图像及其相关元数据,适用于医学影像分析和皮肤病学研究。\n\n**主要类型**:医学影像(皮肤镜图像) \n**涉及器官**:皮肤 \n\n🔗 **开源链接**: \n- [DICOM格式下载](https://challenge.isic-archive.com/data/#2020) \n- [JPEG格式下载](https://challenge.isic-archive.com/data/#2020) \n- [元数据下载](https://challenge.isic-archive.com/data/#2020) \n\n**数据集特点**: \n- 包含33,126张训练图像和10,982张测试图像。 \n- 提供患者ID、性别、年龄和病变部位等元数据。 \n- 遵循CC-BY-NC 4.0国际许可协议。 \n\n**引用方式**: \n如需引用此数据集,请参考以下文献: \nRotemberg, V., et al. (2021). *A patient-centric dataset of images and metadata for identifying melanomas using clinical context*. Sci Data 8, 34. [DOI链接](https://doi.org/10.1038/s41597-021-00815-z) \n\n**注意事项**: \n- 数据集仅限非商业用途。 \n- 使用前需确认已阅读并遵守许可协议。 ",
|
||||
"extraInfo": {
|
||||
"created": 1747382050707,
|
||||
"state": "success",
|
||||
"serverName": "deepseek",
|
||||
"usage": {
|
||||
"prompt_tokens": 2425,
|
||||
"completion_tokens": 317,
|
||||
"total_tokens": 2742,
|
||||
"prompt_tokens_details": {
|
||||
"cached_tokens": 1472
|
||||
},
|
||||
"prompt_cache_hit_tokens": 1472,
|
||||
"prompt_cache_miss_tokens": 953
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -74,7 +176,7 @@
|
||||
"enableWebSearch": false,
|
||||
"temperature": 0.7,
|
||||
"contextLength": 20,
|
||||
"systemPrompt": ""
|
||||
"systemPrompt": "medical data"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,7 @@
|
||||
"outDir": "./dist",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node"
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"paths": {
|
||||
"@/*": [
|
||||
@ -19,4 +18,4 @@
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "src/main.ts"] // 排除 main.ts
|
||||
}
|
||||
}
|
5437
software/package-lock.json
generated
Normal file
5437
software/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@openmcp/electron",
|
||||
"name": "openmcp-electron",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "dist/main.js",
|
||||
@ -13,7 +13,6 @@
|
||||
"email": "1193466151@qq.com"
|
||||
},
|
||||
"dependencies": {
|
||||
"@openmcp/service": "*",
|
||||
"@modelcontextprotocol/sdk": "^1.10.2",
|
||||
"@seald-io/nedb": "^4.1.1",
|
||||
"axios": "^1.7.7",
|
||||
|
@ -33,25 +33,25 @@ function createWindow(): void {
|
||||
const option = getInitConnectionOption();
|
||||
|
||||
// 注册消息接受的管线
|
||||
webview.onDidReceiveMessage((message: any) => {
|
||||
webview.onDidReceiveMessage((message: any) => {
|
||||
console.info(`command: [${message.command || 'No Command'}]`);
|
||||
|
||||
const { command, data } = message;
|
||||
|
||||
switch (command) {
|
||||
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(' '),
|
||||
cwd: option.cwd || ''
|
||||
} :
|
||||
{
|
||||
type: 'SSE',
|
||||
type: 'sse',
|
||||
url: option.url,
|
||||
oauth: option.oauth || ''
|
||||
};
|
||||
|
||||
|
||||
const launchResult = {
|
||||
code: 200,
|
||||
msg: launchResultMessage
|
||||
@ -63,11 +63,11 @@ function createWindow(): void {
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case 'electron/update-connection-signature':
|
||||
|
||||
case 'electron/update-connection-sigature':
|
||||
updateConnectionOption(data);
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
OpenMCPService.routeMessage(command, data, webview);
|
||||
break;
|
||||
@ -94,4 +94,4 @@ app.whenReady().then(() => {
|
||||
|
||||
app.on('window-all-closed', function () {
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
})
|
@ -24,13 +24,13 @@ export class ElectronIPCLike {
|
||||
|
||||
|
||||
interface IStdioLaunchSignature {
|
||||
type: 'STDIO';
|
||||
type: 'stdio';
|
||||
commandString: string;
|
||||
cwd: string;
|
||||
}
|
||||
|
||||
interface ISSELaunchSignature {
|
||||
type:'SSE';
|
||||
type:'sse';
|
||||
url: string;
|
||||
oauth: string;
|
||||
}
|
||||
@ -39,7 +39,7 @@ export type ILaunchSigature = IStdioLaunchSignature | ISSELaunchSignature;
|
||||
|
||||
export function refreshConnectionOption(envPath: string) {
|
||||
const defaultOption = {
|
||||
type:'STDIO',
|
||||
type:'stdio',
|
||||
command: 'mcp',
|
||||
args: ['run', 'main.py'],
|
||||
cwd: '../server'
|
||||
@ -80,7 +80,7 @@ export function updateConnectionOption(data: any) {
|
||||
|
||||
if (data.connectionType === 'STDIO') {
|
||||
const connectionItem = {
|
||||
type: 'STDIO',
|
||||
type: 'stdio',
|
||||
command: data.command,
|
||||
args: data.args,
|
||||
cwd: data.cwd.replace(/\\/g, '/')
|
||||
@ -89,7 +89,7 @@ export function updateConnectionOption(data: any) {
|
||||
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
|
||||
} else {
|
||||
const connectionItem = {
|
||||
type: 'SSE',
|
||||
type: 'sse',
|
||||
url: data.url,
|
||||
oauth: data.oauth
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { setRunningCWD, setVscodeWorkspace } from '../openmcp-sdk/service';
|
||||
import * as OpenMCPService from '../openmcp-sdk/service';
|
||||
import { launch } from './common/entry';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
@ -10,8 +10,8 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||
const workspace = workspaceFolder?.uri.fsPath || '';
|
||||
|
||||
setVscodeWorkspace(workspace);
|
||||
setRunningCWD(context.extensionPath);
|
||||
OpenMCPService.setVscodeWorkspace(workspace);
|
||||
OpenMCPService.setRunningCWD(context.extensionPath);
|
||||
|
||||
launch(context);
|
||||
}
|
||||
@ -19,4 +19,4 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
export function deactivate() {
|
||||
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ export type FsPath = string;
|
||||
export const panels = new Map<FsPath, vscode.WebviewPanel>();
|
||||
|
||||
export interface IStdioConnectionItem {
|
||||
type: 'STDIO';
|
||||
type: 'stdio';
|
||||
name: string;
|
||||
version?: string;
|
||||
command: string;
|
||||
@ -18,7 +18,7 @@ export interface IStdioConnectionItem {
|
||||
}
|
||||
|
||||
export interface ISSEConnectionItem {
|
||||
type: 'SSE';
|
||||
type: 'sse';
|
||||
name: string;
|
||||
version: string;
|
||||
url: string;
|
||||
@ -29,13 +29,13 @@ export interface ISSEConnectionItem {
|
||||
|
||||
|
||||
interface IStdioLaunchSignature {
|
||||
type: 'STDIO';
|
||||
type: 'stdio';
|
||||
commandString: string;
|
||||
cwd: string;
|
||||
}
|
||||
|
||||
interface ISSELaunchSignature {
|
||||
type:'SSE';
|
||||
type:'sse';
|
||||
url: string;
|
||||
oauth: string;
|
||||
}
|
||||
@ -123,7 +123,7 @@ export function getWorkspaceConnectionConfig() {
|
||||
if (item.filePath && item.filePath.startsWith('{workspace}')) {
|
||||
item.filePath = item.filePath.replace('{workspace}', workspacePath).replace(/\\/g, '/');
|
||||
}
|
||||
if (item.type === 'STDIO' && item.cwd && item.cwd.startsWith('{workspace}')) {
|
||||
if (item.type === 'stdio' && item.cwd && item.cwd.startsWith('{workspace}')) {
|
||||
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)) {
|
||||
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, '/');
|
||||
}
|
||||
}
|
||||
@ -213,7 +213,7 @@ export function updateWorkspaceConnectionConfig(
|
||||
|
||||
if (data.connectionType === 'STDIO') {
|
||||
const connectionItem: IStdioConnectionItem = {
|
||||
type: 'STDIO',
|
||||
type: 'stdio',
|
||||
name: data.serverInfo.name,
|
||||
version: data.serverInfo.version,
|
||||
command: data.command,
|
||||
@ -234,7 +234,7 @@ export function updateWorkspaceConnectionConfig(
|
||||
|
||||
} else {
|
||||
const connectionItem: ISSEConnectionItem = {
|
||||
type: 'SSE',
|
||||
type: 'sse',
|
||||
name: data.serverInfo.name,
|
||||
version: data.serverInfo.version,
|
||||
url: data.url,
|
||||
@ -267,7 +267,7 @@ export function updateInstalledConnectionConfig(
|
||||
|
||||
if (data.connectionType === 'STDIO') {
|
||||
const connectionItem: IStdioConnectionItem = {
|
||||
type: 'STDIO',
|
||||
type: 'stdio',
|
||||
name: data.serverInfo.name,
|
||||
version: data.serverInfo.version,
|
||||
command: data.command,
|
||||
@ -287,7 +287,7 @@ export function updateInstalledConnectionConfig(
|
||||
|
||||
} else {
|
||||
const connectionItem: ISSEConnectionItem = {
|
||||
type: 'SSE',
|
||||
type: 'sse',
|
||||
name: data.serverInfo.name,
|
||||
version: data.serverInfo.version,
|
||||
url: data.url,
|
||||
|
@ -52,7 +52,7 @@ export async function validateAndGetCommandPath(commandString: string, cwd?: str
|
||||
|
||||
export async function acquireInstalledConnection(): Promise<IConnectionItem | undefined> {
|
||||
// 让用户选择连接类型
|
||||
const connectionType = await vscode.window.showQuickPick(['STDIO', 'SSE'], {
|
||||
const connectionType = await vscode.window.showQuickPick(['stdio', 'sse'], {
|
||||
placeHolder: '请选择连接类型',
|
||||
canPickMany: false
|
||||
});
|
||||
@ -61,7 +61,7 @@ export async function acquireInstalledConnection(): Promise<IConnectionItem | un
|
||||
return; // 用户取消选择
|
||||
}
|
||||
|
||||
if (connectionType === 'STDIO') {
|
||||
if (connectionType === 'stdio') {
|
||||
// 获取 command
|
||||
const commandString = await vscode.window.showInputBox({
|
||||
prompt: '请输入连接的 command',
|
||||
@ -97,15 +97,15 @@ export async function acquireInstalledConnection(): Promise<IConnectionItem | un
|
||||
|
||||
// 保存连接配置
|
||||
return {
|
||||
type: 'STDIO',
|
||||
name: `STDIO-${Date.now()}`,
|
||||
type: 'stdio',
|
||||
name: `stdio-${Date.now()}`,
|
||||
command: command,
|
||||
args,
|
||||
cwd: cwd || '',
|
||||
filePath: filePath,
|
||||
};
|
||||
|
||||
} else if (connectionType === 'SSE') {
|
||||
} else if (connectionType === 'sse') {
|
||||
// 获取 url
|
||||
const url = await vscode.window.showInputBox({
|
||||
prompt: '请输入连接的 URL',
|
||||
@ -124,8 +124,8 @@ export async function acquireInstalledConnection(): Promise<IConnectionItem | un
|
||||
|
||||
// 保存连接配置
|
||||
return {
|
||||
type: 'SSE',
|
||||
name: `SSE-${Date.now()}`,
|
||||
type: 'sse',
|
||||
name: `sse-${Date.now()}`,
|
||||
version: '1.0', // 假设默认版本为 1.0,可根据实际情况修改
|
||||
url: url,
|
||||
oauth: oauth || ''
|
||||
|
@ -6,7 +6,7 @@ import * as vscode from 'vscode';
|
||||
|
||||
export async function acquireUserCustomConnection(): Promise<IConnectionItem | undefined> {
|
||||
// 让用户选择连接类型
|
||||
const connectionType = await vscode.window.showQuickPick(['STDIO', 'SSE'], {
|
||||
const connectionType = await vscode.window.showQuickPick(['stdio', 'sse'], {
|
||||
placeHolder: '请选择连接类型'
|
||||
});
|
||||
|
||||
@ -14,7 +14,7 @@ export async function acquireUserCustomConnection(): Promise<IConnectionItem | u
|
||||
return; // 用户取消选择
|
||||
}
|
||||
|
||||
if (connectionType === 'STDIO') {
|
||||
if (connectionType === 'stdio') {
|
||||
// 获取 command
|
||||
const commandString = await vscode.window.showInputBox({
|
||||
prompt: '请输入连接的 command',
|
||||
@ -47,15 +47,15 @@ export async function acquireUserCustomConnection(): Promise<IConnectionItem | u
|
||||
|
||||
// 保存连接配置
|
||||
return {
|
||||
type: 'STDIO',
|
||||
name: `STDIO-${Date.now()}`,
|
||||
type: 'stdio',
|
||||
name: `stdio-${Date.now()}`,
|
||||
command: command,
|
||||
args,
|
||||
cwd: cwd || '',
|
||||
filePath
|
||||
};
|
||||
|
||||
} else if (connectionType === 'SSE') {
|
||||
} else if (connectionType === 'sse') {
|
||||
// 获取 url
|
||||
const url = await vscode.window.showInputBox({
|
||||
prompt: '请输入连接的 URL',
|
||||
@ -74,8 +74,8 @@ export async function acquireUserCustomConnection(): Promise<IConnectionItem | u
|
||||
|
||||
// 保存连接配置
|
||||
return {
|
||||
type: 'SSE',
|
||||
name: `SSE-${Date.now()}`,
|
||||
type: 'sse',
|
||||
name: `sse-${Date.now()}`,
|
||||
version: '1.0', // 假设默认版本为 1.0,可根据实际情况修改
|
||||
url: url,
|
||||
oauth: oauth || ''
|
||||
|
@ -5,26 +5,24 @@ import { getWorkspaceConnectionConfigItemByPath } from '../global';
|
||||
|
||||
export class WebviewController {
|
||||
@RegisterCommand('openmcp.showOpenMCP')
|
||||
async showOpenMCP(context: vscode.ExtensionContext, uri: vscode.Uri) {
|
||||
async showOpenMCP(context: vscode.ExtensionContext, uri: vscode.Uri) {
|
||||
const connectionItem = getWorkspaceConnectionConfigItemByPath(uri.fsPath);
|
||||
|
||||
if (!connectionItem) {
|
||||
// 项目不存在连接信息
|
||||
const cwd = getLaunchCWD(context, uri);
|
||||
|
||||
const signature = getDefaultLanunchSignature(uri.fsPath, cwd);
|
||||
const sigature = getDefaultLanunchSignature(uri.fsPath, cwd);
|
||||
|
||||
if (!signature) {
|
||||
vscode.window.showInformationMessage('OpenMCP: 无法获取启动参数');
|
||||
if (!sigature) {
|
||||
vscode.window.showErrorMessage('OpenMCP: 无法获取启动参数');
|
||||
return;
|
||||
}
|
||||
|
||||
revealOpenMcpWebviewPanel(context, 'workspace', uri.fsPath, {
|
||||
type: 'STDIO',
|
||||
type: 'stdio',
|
||||
name: 'OpenMCP',
|
||||
command: signature.command,
|
||||
args: signature.args,
|
||||
command: sigature.command,
|
||||
args: sigature.args,
|
||||
cwd
|
||||
});
|
||||
} else {
|
||||
|
@ -2,25 +2,18 @@ import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import * as fspath from 'path';
|
||||
import { IConnectionItem, ILaunchSigature, panels, updateInstalledConnectionConfig, updateWorkspaceConnectionConfig } from '../global';
|
||||
import { routeMessage } from '../../openmcp-sdk/service';
|
||||
import * as OpenMCPService from '../../openmcp-sdk/service';
|
||||
|
||||
export function getWebviewContent(context: vscode.ExtensionContext, panel: vscode.WebviewPanel): string | undefined {
|
||||
const viewRoot = fspath.join(context.extensionPath, 'openmcp-sdk', 'renderer');
|
||||
const htmlIndexPath = fspath.join(viewRoot, 'index.html');
|
||||
|
||||
const htmlIndexPath = fspath.join(viewRoot, 'index.html');
|
||||
const html = fs.readFileSync(htmlIndexPath, { encoding: 'utf-8' })?.replace(/(<link.+?href="|<script.+?src="|<img.+?src="|url\()(.+?)(\)|")/g, (m, $1, $2) => {
|
||||
const importFile = $2 as string;
|
||||
const rel = importFile.startsWith('/') ? importFile.substring(1) : importFile;
|
||||
const absLocalPath = fspath.resolve(viewRoot, rel);
|
||||
|
||||
const absLocalPath = fspath.resolve(viewRoot, $2);
|
||||
const webviewUri = panel.webview.asWebviewUri(vscode.Uri.file(absLocalPath));
|
||||
const replaceHref = $1 + webviewUri?.toString() + '"';
|
||||
|
||||
const replaceHref = $1 + webviewUri?.toString() + '"';
|
||||
return replaceHref;
|
||||
});
|
||||
|
||||
console.log(html);
|
||||
|
||||
|
||||
});
|
||||
return html;
|
||||
}
|
||||
|
||||
@ -36,7 +29,7 @@ export function revealOpenMcpWebviewPanel(
|
||||
type: 'workspace' | 'installed',
|
||||
panelKey: string,
|
||||
option: IConnectionItem = {
|
||||
type: 'STDIO',
|
||||
type: 'stdio',
|
||||
name: 'OpenMCP',
|
||||
command: 'mcp',
|
||||
args: ['run', 'main.py']
|
||||
@ -63,9 +56,9 @@ export function revealOpenMcpWebviewPanel(
|
||||
|
||||
|
||||
// 设置HTML内容
|
||||
const html = getWebviewContent(context, panel);
|
||||
const html = getWebviewContent(context, panel);
|
||||
panel.webview.html = html || '';
|
||||
panel.iconPath = vscode.Uri.file(fspath.join(context.extensionPath, 'openmcp-sdk', 'renderer', 'images', 'openmcp.png'));
|
||||
panel.iconPath = vscode.Uri.file(fspath.join(context.extensionPath, 'openmcp-sdk', 'renderer', 'images', 'openmcp.png'));
|
||||
|
||||
// 处理来自webview的消息
|
||||
panel.webview.onDidReceiveMessage(message => {
|
||||
@ -75,18 +68,18 @@ export function revealOpenMcpWebviewPanel(
|
||||
// 拦截消息,注入额外信息
|
||||
switch (command) {
|
||||
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(' '),
|
||||
cwd: option.cwd || ''
|
||||
} :
|
||||
{
|
||||
type: 'SSE',
|
||||
type: 'sse',
|
||||
url: option.url,
|
||||
oauth: option.oauth || ''
|
||||
};
|
||||
|
||||
|
||||
const launchResult = {
|
||||
code: 200,
|
||||
msg: launchResultMessage
|
||||
@ -98,8 +91,8 @@ export function revealOpenMcpWebviewPanel(
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case 'vscode/update-connection-signature':
|
||||
|
||||
case 'vscode/update-connection-sigature':
|
||||
if (type === 'installed') {
|
||||
updateInstalledConnectionConfig(panelKey, data);
|
||||
} else {
|
||||
@ -108,7 +101,7 @@ export function revealOpenMcpWebviewPanel(
|
||||
break;
|
||||
|
||||
default:
|
||||
routeMessage(command, data, panel.webview);
|
||||
OpenMCPService.routeMessage(command, data, panel.webview);
|
||||
break;
|
||||
}
|
||||
|
||||
|
38
turbo.json
38
turbo.json
@ -1,38 +0,0 @@
|
||||
{
|
||||
"$schema": "https://turborepo.com/schema.json",
|
||||
"tasks": {
|
||||
"serve": {
|
||||
"cache": false
|
||||
},
|
||||
"build": {
|
||||
"dependsOn": ["@openmcp/renderer#build", "@openmcp/service#build", "//#build:task-loop"],
|
||||
"cache": false
|
||||
},
|
||||
"//#build:task-loop": {
|
||||
"cache": false
|
||||
},
|
||||
"@openmcp/renderer#serve": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
},
|
||||
"@openmcp/renderer#build": {
|
||||
"cache": false
|
||||
},
|
||||
"@openmcp/service#serve": {
|
||||
"persistent": true,
|
||||
"cache": false
|
||||
},
|
||||
"@openmcp/service#build": {
|
||||
"cache": false
|
||||
},
|
||||
"@openmcp/electron#dev": {
|
||||
"dependsOn": ["@openmcp/renderer#build"],
|
||||
"persistent": true,
|
||||
"cache": false
|
||||
},
|
||||
"@openmcp/electron#build": {
|
||||
"dependsOn": ["@openmcp/renderer#build"],
|
||||
"outputs": ["dist/**"]
|
||||
}
|
||||
}
|
||||
}
|
@ -23,12 +23,7 @@ module.exports = {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: {
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
configFile: path.resolve(__dirname, '../tsconfig.json') // 指定 tsconfig.json 路径
|
||||
}
|
||||
},
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user