Compare commits

...

193 Commits

Author SHA1 Message Date
db200f9601 transfer website domain 2025-07-11 21:02:44 +08:00
kirigaya
43a19b1d11 fix issue 48 2025-07-08 18:14:22 +08:00
kirigaya
3fa5db53de update readme 2025-07-08 18:11:25 +08:00
Kirigaya Kazuto
7263baf956
Merge pull request #47 from LSTM-Kirigaya/dev
Dev
2025-07-06 16:52:13 +08:00
793262f8b1 test news page 2025-07-06 16:47:41 +08:00
bc38ea49bb test news page 2025-07-06 06:08:05 +08:00
1244ee474e test news page 2025-07-06 05:37:23 +08:00
5bf0623327 test news page 2025-07-06 05:26:11 +08:00
e43431a725 add news page 2025-07-05 05:49:40 +08:00
30937087dd add news page 2025-07-05 05:43:58 +08:00
b3ce96c77d add news page 2025-07-05 05:37:47 +08:00
6e8b4aa23c add news page 2025-07-05 05:26:07 +08:00
2ce5830df9 add news page 2025-07-05 05:24:56 +08:00
li1553770945
3cb6cfd6d2 feat:只保留 main 分支的 CICD 2025-07-04 10:57:08 +08:00
690dde1cd0 finish auto detection 2025-07-04 02:47:34 +08:00
Kirigaya Kazuto
d9928ecc5d
Merge pull request #46 from LSTM-Kirigaya/dev
Some checks failed
Build / build (ubuntu-latest) (push) Has been cancelled
Test / test (macos-latest) (push) Has been cancelled
Test / test (ubuntu-latest) (push) Has been cancelled
Test / test (windows-latest) (push) Has been cancelled
Dev
2025-07-03 20:16:59 +08:00
18c934b983 fix issue 2025-07-03 19:59:55 +08:00
e478ae95f2 support self-check 2025-07-03 19:48:40 +08:00
8a6d555da1 support self-check 2025-07-03 19:15:53 +08:00
13673d798b support self-check 2025-07-03 19:06:55 +08:00
25f74b8f1e support self-check 2025-07-03 17:18:54 +08:00
dcabd47a20 support self-check 2025-07-03 13:48:58 +08:00
9294275874 finish auto detection 2025-07-03 03:32:40 +08:00
92c8cf90ed support tomology detection 2025-07-02 23:02:09 +08:00
4a210eaa82 support tomology detection 2025-07-02 21:37:33 +08:00
a735bbf023 finish work 2025-07-02 04:56:41 +08:00
27c3f24089 finish work 2025-07-01 03:32:22 +08:00
fba7ed29f0 finish work 2025-07-01 02:46:14 +08:00
60196889bb finish work 2025-07-01 00:17:14 +08:00
316c73613c support auto detect 2025-06-30 22:16:47 +08:00
d3e01376f0 support ai-mook 2025-06-30 20:54:03 +08:00
c366494637 support ai-mook 2025-06-30 20:29:10 +08:00
c8a7ac76b9 support ai-mook 2025-06-30 20:28:35 +08:00
34a6001455 support ai-mook 2025-06-30 20:25:16 +08:00
li1553770945
7ff6f3b48e feat:vscode环境下根据设置语言自动选择l10n文件 2025-06-29 22:55:50 +08:00
li1553770945
318cf64ace feat:在没有打开工作区时,相关功能弹出错误提示 2025-06-29 22:50:36 +08:00
1ce5b3a60a modify page layout in prompt, resource and tool 2025-06-29 22:44:53 +08:00
47d5d6474a finish mook 2025-06-29 22:30:52 +08:00
1f7e5b8b7d update 2025-06-29 22:26:18 +08:00
li1553770945
66e1b54cc3 feat:新建的临时项目连接成功后会被替换而不是保留 2025-06-29 22:11:43 +08:00
li1553770945
21eea00818 fix:移除无用的stdio校验 2025-06-29 21:36:42 +08:00
4b3bbbed66 update 2025-06-26 01:05:26 +08:00
af3b2cbee5 finish post test
Some checks failed
Build / build (ubuntu-latest) (push) Has been cancelled
Test / test (macos-latest) (push) Has been cancelled
Test / test (ubuntu-latest) (push) Has been cancelled
Test / test (windows-latest) (push) Has been cancelled
2025-06-22 20:01:49 +08:00
ba70b08866 finish post test
Some checks are pending
Build / build (ubuntu-latest) (push) Waiting to run
Test / test (macos-latest) (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
Test / test (windows-latest) (push) Waiting to run
2025-06-22 19:58:24 +08:00
d9370cc688 fix i18n
Some checks are pending
Build / build (ubuntu-latest) (push) Waiting to run
Test / test (macos-latest) (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
Test / test (windows-latest) (push) Waiting to run
2025-06-22 17:51:03 +08:00
Kirigaya Kazuto
e09c8beb7e
Merge pull request #43 from LSTM-Kirigaya/dev
Some checks are pending
Build / build (ubuntu-latest) (push) Waiting to run
Test / test (macos-latest) (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
Test / test (windows-latest) (push) Waiting to run
update yarn
2025-06-22 17:11:51 +08:00
9abc679c8f update yarn 2025-06-22 16:59:38 +08:00
li1553770945
f9fd4db580 fix:添加vsce作为项目依赖 2025-06-22 16:49:20 +08:00
Li Yaning
eab59e4ba1
Merge pull request #42 from LSTM-Kirigaya/dev
包管理器换成yarn
2025-06-22 16:32:04 +08:00
li1553770945
d3ae4c7999 Merge branch 'dev' of https://github.com/LSTM-Kirigaya/openmcp-client into dev 2025-06-22 16:26:35 +08:00
li1553770945
ed4d5e2ed1 Merge branch 'main' into dev 2025-06-22 16:25:37 +08:00
Li Yaning
216dabec6f
Merge pull request #41 from LSTM-Kirigaya/hotfix
包管理器换成yarn
2025-06-22 16:21:44 +08:00
li1553770945
279768e320 feat:包管理器改为yarn 2025-06-22 16:15:11 +08:00
li1553770945
ef811c1e8a fix:修复依赖问题 2025-06-22 15:32:27 +08:00
li1553770945
1702f93d16 feat:添加连接测试 2025-06-22 15:23:41 +08:00
li1553770945
c96d995f11 fix:修复依赖版本问题 2025-06-22 15:17:33 +08:00
li1553770945
15f0ff91e3 fix:添加缺失依赖 2025-06-22 14:55:32 +08:00
Kirigaya Kazuto
dd7685e42c
Merge pull request #40 from LSTM-Kirigaya/dev
Dev
2025-06-22 04:21:09 +08:00
ae56771c5a finish config export 2025-06-22 04:06:21 +08:00
10ffe098c2 finish config export 2025-06-22 04:03:29 +08:00
d10c88f35e finish config export 2025-06-22 04:00:14 +08:00
Kirigaya Kazuto
22a79cfc55
Merge pull request #39 from LSTM-Kirigaya/dev
Dev
2025-06-20 21:20:37 +08:00
3e363c6dde finish sdk 0.0.8 2025-06-20 21:20:18 +08:00
4a8385d7ad task loop 2025-06-20 21:06:01 +08:00
be641197b5 add prompt reader 2025-06-20 18:19:20 +08:00
7098382317 upgrade taskloop 2025-06-20 17:23:45 +08:00
51ceacbc10 upgrade taskloop 2025-06-20 17:04:26 +08:00
50510ae647 upgrade taskloop 2025-06-20 15:49:54 +08:00
f59e27b569 fix error 2025-06-20 14:35:07 +08:00
10befe12b4 save 2025-06-20 13:54:04 +08:00
7968a0d4c5 finish new agent framework 2025-06-20 02:42:29 +08:00
f51bd364b4 finish new agent framework 2025-06-20 02:36:53 +08:00
66f9b128f8 update 2025-06-19 23:56:08 +08:00
Kirigaya Kazuto
d04c04f574
Merge pull request #38 from LSTM-Kirigaya/dev
Dev
2025-06-19 15:20:34 +08:00
a5fe596cac task loop 2025-06-19 15:19:31 +08:00
e6d1f1205a task loop 2025-06-19 14:24:43 +08:00
Meghan Morrow
1b3418f15b 修复: 添加工作区验证以防止.openmcp目录创建错误
- 在getWorkspaceConnectionConfigPath()中添加工作区路径验证
- 在getWorkspaceConnectionConfig()中添加工作区路径验证
- 当没有找到工作区时抛出描述性错误
- 解决VSCode扩展尝试创建.openmcp目录时的ENOENT错误
2025-06-19 14:24:43 +08:00
Meghan Morrow
0961f18381 refactor: 更新task-loop注释,移除过时的XML工具调用说明
- 删除handleChunkDeltaContent函数注释中关于XML指令包裹的toolcall描述
- 该功能现在在toolcalled生命周期后处理,而非chunk生命周期中处理
2025-06-19 14:24:43 +08:00
9449bdb190 support xml 2025-06-19 14:24:43 +08:00
d4ac089a9a support xml 2025-06-19 14:24:43 +08:00
0df86cfc28 change tooltip color 2025-06-19 14:24:43 +08:00
c9a2d47c85 task loop 2025-06-19 14:24:24 +08:00
Li Yaning
51d4495f88
Merge pull request #37 from LSTM-Kirigaya/main
feat:增加测试样例
2025-06-19 11:01:02 +08:00
Meghan Morrow
8446cf07dc 修复: 添加工作区验证以防止.openmcp目录创建错误
- 在getWorkspaceConnectionConfigPath()中添加工作区路径验证
- 在getWorkspaceConnectionConfig()中添加工作区路径验证
- 当没有找到工作区时抛出描述性错误
- 解决VSCode扩展尝试创建.openmcp目录时的ENOENT错误
2025-06-19 10:55:06 +08:00
li1553770945
61a82061d7 feat:增加测试样例 2025-06-19 00:26:53 +08:00
Meghan Morrow
777a17bb99 refactor: 更新task-loop注释,移除过时的XML工具调用说明
- 删除handleChunkDeltaContent函数注释中关于XML指令包裹的toolcall描述
- 该功能现在在toolcalled生命周期后处理,而非chunk生命周期中处理
2025-06-18 22:17:35 +08:00
8355e0ec66 support xml 2025-06-18 21:47:02 +08:00
2c13ce5e6d support xml 2025-06-18 20:32:04 +08:00
c2a634118b change tooltip color 2025-06-18 17:04:33 +08:00
3700857aa8 update 2025-06-17 23:32:50 +08:00
94935e6b9c update 2025-06-17 23:29:23 +08:00
50990688be update 2025-06-17 23:12:37 +08:00
li1553770945
2a8ff295b2 feat:构建工具改为esbuild 2025-06-15 23:59:39 +08:00
Kirigaya Kazuto
3cae8572e8
Merge pull request #35 from LSTM-Kirigaya/claude
Claude
2025-06-15 20:53:47 +08:00
5bfda7a893 finish ocr adjust 2025-06-15 20:53:19 +08:00
d26201c893 finish ocr adjust 2025-06-15 19:55:39 +08:00
Meghan Morrow
dad8d41e46 feat: 工具模块界面优化 + OpenRouter集成
- 优化工具列表显示:工具名称优先显示,描述悬停提示
- 集成OpenRouter支持:400+模型一键访问
- 改进模型选择界面:支持搜索过滤和分类显示
- 自动同步新提供商配置
- 版本升级至0.1.7

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-15 19:27:54 +08:00
596e42e110 recover task-loop package 2025-06-15 19:18:56 +08:00
e22d344647 recover task-loop package 2025-06-15 18:55:54 +08:00
li1553770945
62ea963003 feat:更新github actions 2025-06-15 00:31:09 +08:00
Meghan Morrow
2af555dd3b feat: add MCP security checks to MVP requirements
Added MCP security checks (prevent prompt injection, etc.) to the MVP roadmap in README to enhance security features.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-15 00:25:05 +08:00
li1553770945
0560fda4bb feat:添加测试程序(未添加测试用例) 2025-06-15 00:18:17 +08:00
dcbcf567cc update 2025-06-14 00:13:46 +08:00
Li Yaning
7e203905de
Merge pull request #33 from ZYD045692/feature/mcp-hot-update-stdio
feat(service-mcp-connect): 实现stdio连接方式的热更新
2025-06-14 00:08:24 +08:00
li1553770945
52074d1ea2 feat:增加自动构建脚本 2025-06-14 00:02:53 +08:00
Li Yaning
66e748bf01
Merge pull request #34 from LSTM-Kirigaya/hotfix
构建工具改为rollup
2025-06-13 23:28:52 +08:00
li1553770945
836fc6d7b0 Merge branch 'main' of https://github.com/LSTM-Kirigaya/openmcp-client into hotfix 2025-06-13 23:28:24 +08:00
li1553770945
786f5ea615 feat:构建工具改为rollup 2025-06-13 23:07:27 +08:00
li1553770945
e7856dd8a6 fix 2025-06-13 21:27:37 +08:00
li1553770945
bd23bfe17c fix 2025-06-13 21:22:49 +08:00
li1553770945
dfb70785e0 bugfix:尝试修复路径问题 2025-06-13 21:12:49 +08:00
aa8547c4ad fix 0.1.5 cannot launch in windows 2025-06-13 21:08:44 +08:00
54826eda62 fix 0.1.5 cannot launch in windows 2025-06-13 18:11:17 +08:00
506d33be9a fix 0.1.5 cannot launch in windows 2025-06-13 18:05:58 +08:00
ZYD045692
17cc8f7612 feat(service-mcp-connect): 实现stdio连接方式的热更新 2025-06-13 17:40:26 +08:00
0f32993edb fix bug of env 2025-06-12 13:53:26 +08:00
1a11ca64e5 update 2025-06-11 22:36:00 +08:00
0ea7e1053c modify about page 2025-06-11 00:45:56 +08:00
Li Yaning
cb3322d354
hoxfix:修改readme中的错误 2025-06-10 22:32:13 +08:00
c31a6a39c7 update readme 2025-06-10 02:29:42 +08:00
9cb6387fe2 update readme 2025-06-10 02:26:25 +08:00
e4bf1c8fd6 update readme 2025-06-10 02:20:13 +08:00
3ceb5a7b06 支持其他语言 2025-06-09 17:54:50 +08:00
fb092c8ad6 updatre 2025-06-06 21:07:35 +08:00
35a0824423 save 2025-06-06 20:21:00 +08:00
kirigaya
d973d8437f cancel axios adapter 2025-06-05 20:21:31 +08:00
0c50ebcc2c publish openmcp-sdk 0.0.6 2025-06-05 17:21:37 +08:00
e65ebe3b81 support on windows 2025-06-04 22:56:21 +08:00
5361f88381 release openmcp-sdk 0.0.5 2025-06-04 22:22:07 +08:00
871ff15b41 release openmcp-sdk 0.0.5 2025-06-04 21:28:24 +08:00
e3e2ed99f8 modify config file 2025-06-04 21:25:37 +08:00
cf5720c541 modify config fiel 2025-06-04 20:19:21 +08:00
1c3724f2ed remove dynamic vue created in ts 2025-06-04 19:23:51 +08:00
528ab45280 remove dynamic vue created in ts 2025-06-04 19:20:51 +08:00
e3ccb69b92 merge 2025-06-04 17:08:37 +08:00
9d94a5b865 save 2025-06-04 17:07:29 +08:00
68db65c61b merge & fix tsx lanuch issue 2025-06-04 00:17:26 +08:00
li1553770945
167c791452 fix:修复依赖问题 2025-06-03 22:24:20 +08:00
Li Yaning
547d07c8fa
Merge pull request #29 from LSTM-Kirigaya/feat/auth
完成了Oauth认证
2025-06-03 21:51:43 +08:00
Li Yaning
740293ab74
Merge branch 'main' into feat/auth 2025-06-03 21:51:26 +08:00
li1553770945
fe2a7c68cb bugfix:修复配置文件不符合要求导致整个插件崩溃的bug 2025-06-03 21:24:50 +08:00
db7b8273db fix gemini model list get bug 2025-06-03 20:29:29 +08:00
305a35f963 support grok3 & support model update 2025-06-03 15:32:15 +08:00
c252ed0b9f support grok3 & support model update 2025-06-03 15:25:39 +08:00
f8447cadb6 support google gemini 2025-06-02 19:37:54 +08:00
1b85207b7f support google gemini 2025-06-02 18:03:19 +08:00
d1a4b506ce fix issue#25 2025-06-01 22:33:22 +08:00
7b3daacdf5 fix issue#21 2025-06-01 22:20:21 +08:00
6ab0c789b4 fix bug of saving 2025-06-01 16:48:14 +08:00
e4e626cd4e fix bug of object save 2025-06-01 15:32:56 +08:00
34bc18085a add codemirror 2025-06-01 15:26:26 +08:00
97f89b0833 fix bug 2025-06-01 03:40:16 +08:00
li1553770945
46115bcfba feat:更新导入方式 2025-05-30 00:26:11 +08:00
li1553770945
ae7eb45403 feat:更新导入方式 2025-05-30 00:20:58 +08:00
Li Yaning
dd4331cdaa
Merge pull request #23 from LSTM-Kirigaya/main
merge main
2025-05-30 00:02:08 +08:00
li1553770945
054017bfe5 feat(auth):实现OAuth认证 2025-05-30 00:01:29 +08:00
ab81e276d3 change document link 2025-05-29 19:55:47 +08:00
li1553770945
c50c75821c feat: 迁移至ES模块(ESM)并配置TSX支持 2025-05-29 02:02:14 +08:00
667e000afe update discord link 2025-05-27 18:58:00 +08:00
li1553770945
df2716fa7a Merge branch 'feat/auth' of https://github.com/LSTM-Kirigaya/openmcp-client into feat/auth 2025-05-26 21:41:13 +08:00
li1553770945
b1f0675b5e fix:修复streamable_http连接显示失败的bug 2025-05-26 21:41:09 +08:00
Li Yaning
fc68a12b30
Merge pull request #20 from LSTM-Kirigaya/main
sync
2025-05-26 21:40:31 +08:00
b4544f08f4 fix some bugs 2025-05-26 02:04:19 +08:00
kirigaya
f0b8b88fbc fix some bug 2025-05-25 23:04:41 +08:00
kirigaya
13d05462fe fix some bug 2025-05-25 22:19:00 +08:00
e98ad038c5 Merge branch 'main' of https://github.com/LSTM-Kirigaya/openmcp-client 2025-05-23 19:14:15 +08:00
d1a392bb87 修改 mcp 的引入方式 2025-05-23 19:14:11 +08:00
AmeZora
ac9bd43654
Update README.md 2025-05-23 01:17:48 +08:00
0e057bec1a 修改 mcp 的引入方式 2025-05-22 16:50:13 +08:00
bca49f01de 增加拖拽添加连接的功能 2025-05-22 16:11:16 +08:00
78634e6327 增加拖拽添加连接的功能 2025-05-22 16:05:16 +08:00
043ca8ce1d 增加拖拽添加连接的功能 2025-05-22 16:04:00 +08:00
fed2e6d27c 修复剪贴板的问题 2025-05-22 14:50:11 +08:00
a132fd41fe 0.1.0 完成 vscode 插件端的改造 2025-05-22 03:38:39 +08:00
b853118fa0 0.1.0 完成 vscode 插件端的改造 2025-05-22 03:31:18 +08:00
df8b4df2c0 0.1.0 完成 vscode 插件端的改造 2025-05-22 02:56:55 +08:00
f37b8babcd 0.1.0 所有 mvp 完成 2025-05-21 20:29:50 +08:00
4017fc3290 重构完成基础设施 2025-05-21 16:55:51 +08:00
86c218ab5e 优化 service, 进行 mcp 连接复用 2025-05-21 01:51:27 +08:00
468ce23b66 重构完成基础设施 2025-05-20 20:41:00 +08:00
4d459464d3 update 2025-05-20 03:47:15 +08:00
li1553770945
8b3816d05c Merge branch 'main' of https://github.com/LSTM-Kirigaya/openmcp-client 2025-05-19 23:39:29 +08:00
c218dcba03 完成兼容 2025-05-19 23:34:10 +08:00
355b25b9b7 完成兼容 2025-05-19 22:14:13 +08:00
be48449df8 merge multi clients 2025-05-19 21:11:34 +08:00
4597904796 update 2025-05-19 21:10:09 +08:00
kirigaya
f84834a97f update lock 2025-05-19 20:49:40 +08:00
c9bc58ca9c finish 0.0.9; prepare test TIP CI system 2025-05-19 20:46:04 +08:00
9a687e4432 fix oom 2025-05-19 20:10:23 +08:00
kirigaya
c8451241f3 更新 vite 后调整 task-loop 的打包策略 2025-05-19 17:22:06 +08:00
kirigaya
3924ee0224 更新 vite 后调整 task-loop 的打包策略 2025-05-19 17:08:39 +08:00
kirigaya
13fd6a00e1 update 2025-05-19 16:47:44 +08:00
19857a5c73 update 2025-05-19 16:07:50 +08:00
239 changed files with 30856 additions and 17270 deletions

42
.github/workflows/build.yaml vendored Normal file
View File

@ -0,0 +1,42 @@
name: Build
on:
push:
branches:
- main
release:
types:
- published
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22.x'
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Build all
run: yarn build
- name: Package VSIX
run: npx vsce package --out dist/openmcp.vsix
- name: Upload VSIX
uses: actions/upload-artifact@v4
with:
name: openmcp-${{ matrix.os }}
path: dist/openmcp.vsix

41
.github/workflows/test.yaml vendored Normal file
View File

@ -0,0 +1,41 @@
name: Test
on:
push:
branches:
- main
release:
types:
- published
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22.x'
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Install xvfb (Linux only)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y xvfb
- name: Run Test with XVFB (Linux)
if: runner.os == 'Linux'
run: xvfb-run --auto-servernum yarn test
- name: Run Test (Windows/macOS)
if: runner.os != 'Linux'
run: yarn test

4
.gitignore vendored
View File

@ -15,3 +15,7 @@ resources/renderer
resources/service resources/service
*.traineddata *.traineddata
.turbo .turbo
stats.html
.openmcp
test-vsix
resources/changelog/**

View File

@ -1 +0,0 @@
{"items":[]}

View File

@ -11,9 +11,6 @@
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off", "typescript.tsc.autoDetect": "off",
"i18n-haru.root": "renderer/src/i18n", "i18n-haru.root": "./renderer/src/i18n",
"i18n-haru.main": "zh", "i18n-haru.main": "zh"
"i18n-ally.localesPaths": [
"renderer/src/i18n"
]
} }

File diff suppressed because one or more lines are too long

View File

@ -1,387 +0,0 @@
{
"currentIndex": 3,
"tabs": [
{
"name": "交互测试",
"icon": "icon-robot",
"type": "blank",
"componentIndex": 3,
"storage": {
"messages": [
{
"role": "user",
"content": "总结  https://asoca.grand-challenge.org/ ",
"extraInfo": {
"created": 1747379137481,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_2z36dkpt4m5jaioqjsbjabiw",
"index": 0,
"type": "function",
"function": {
"name": "k_navigate",
"arguments": "{\"url\":\"https://asoca.grand-challenge.org/\"}"
}
}
],
"extraInfo": {
"created": 1747379138355,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "tool",
"index": 0,
"tool_call_id": "call_2z36dkpt4m5jaioqjsbjabiw",
"content": [
{
"type": "text",
"text": "Navigated to https://asoca.grand-challenge.org/"
}
],
"extraInfo": {
"created": 1747379139028,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "assistant",
"content": "现在我将总结 https://asoca.grand-challenge.org/ 的内容。\n\n首先我需要浏览页面并提取相关信息。接下来我将执行以下步骤\n\n1. 获取页面的标题和主要描述。\n2. 提取页面中的关键信息,如项目目标、参与方式、时间安排等。\n3. 总结页面的主要内容和目的。\n\n让我开始执行这些步骤。",
"tool_calls": [
{
"id": "call_zv6y2wsmzqn9ei7y8oywt75r",
"index": 0,
"type": "function",
"function": {
"name": "k_evaluate",
"arguments": "{\"script\":\"document.title\"}"
}
},
{
"id": "call_uzg4bj50l2ekjaonrkj8r852",
"index": 1,
"type": "function",
"function": {
"name": "k_evaluate",
"arguments": "{\"script\":\"document.querySelector('meta[name=\\\"description\\\"]').content\"}"
}
},
{
"id": "call_q3jqivcv8vgfpmk4u7w9k9au",
"index": 2,
"type": "function",
"function": {
"name": "k_evaluate",
"arguments": "{\"script\":\"Array.from(document.querySelectorAll('h1, h2, h3, p')).map(el => el.textContent.trim()).join('\\\\n')\"}"
}
}
],
"extraInfo": {
"created": 1747379142638,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "tool",
"index": 0,
"tool_call_id": "call_zv6y2wsmzqn9ei7y8oywt75r",
"content": [
{
"type": "text",
"text": "Execution result:\n\"ERROR: The request could not be satisfied\"\n\nConsole output:\n"
}
],
"extraInfo": {
"created": 1747379142650,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "tool",
"index": 1,
"tool_call_id": "call_uzg4bj50l2ekjaonrkj8r852",
"content": [
{
"type": "text",
"text": "Script execution failed: Cannot read properties of null (reading 'content')"
}
],
"extraInfo": {
"created": 1747379142656,
"state": "tool call failed",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "tool",
"index": 2,
"tool_call_id": "call_q3jqivcv8vgfpmk4u7w9k9au",
"content": [
{
"type": "text",
"text": "Execution result:\n\"403 ERROR\\nThe request could not be satisfied.\"\n\nConsole output:\n"
}
],
"extraInfo": {
"created": 1747379142664,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_w7iq0mszlm46ooo06w9fvri4",
"index": 0,
"type": "function",
"function": {
"name": "k_navigate",
"arguments": "{\"url\":\"https://asoca.grand-challenge.org/\",\"launchOptions\":{\"headless\":false,\"args\":[\"--no-sandbox\"]},\"allowDangerous\":true}"
}
}
],
"extraInfo": {
"created": 1747379144069,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "tool",
"index": 0,
"tool_call_id": "call_w7iq0mszlm46ooo06w9fvri4",
"content": [
{
"type": "text",
"text": "Navigated to https://asoca.grand-challenge.org/"
}
],
"extraInfo": {
"created": 1747379147016,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_qelzom3lpyo7skuil4b469t9",
"index": 0,
"type": "function",
"function": {
"name": "k_evaluate",
"arguments": "{\"script\":\"document.title\"}"
}
},
{
"id": "call_wtla8rzqnes1gffk7wieubxk",
"index": 1,
"type": "function",
"function": {
"name": "k_evaluate",
"arguments": "{\"script\":\"document.querySelector('meta[name=\\\"description\\\"]').content\"}"
}
},
{
"id": "call_z6wcwspc9lqk4w3sfknv30um",
"index": 2,
"type": "function",
"function": {
"name": "k_evaluate",
"arguments": "{\"script\":\"Array.from(document.querySelectorAll('h1, h2, h3, p')).map(el => el.textContent.trim()).join('\\\\n')\"}"
}
}
],
"extraInfo": {
"created": 1747379148961,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "tool",
"index": 0,
"tool_call_id": "call_qelzom3lpyo7skuil4b469t9",
"content": [
{
"type": "text",
"text": "Execution result:\n\"\"\n\nConsole output:\n"
}
],
"extraInfo": {
"created": 1747379149424,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "tool",
"index": 1,
"tool_call_id": "call_wtla8rzqnes1gffk7wieubxk",
"content": [
{
"type": "text",
"text": "Script execution failed: Cannot read properties of null (reading 'content')"
}
],
"extraInfo": {
"created": 1747379149428,
"state": "tool call failed",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "tool",
"index": 2,
"tool_call_id": "call_z6wcwspc9lqk4w3sfknv30um",
"content": [
{
"type": "text",
"text": "Execution result:\n\"\"\n\nConsole output:\n"
}
],
"extraInfo": {
"created": 1747379149432,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "assistant",
"content": "由此可见,网站 https://asoca.grand-challenge.org/ 可能有一些限制或反爬虫机制,导致无法直接获取页面内容。建议通过以下方式进一步分析:\n\n1. **检查页面加载状态**:确认页面是否完全加载。\n2. **查看网络请求**:检查是否有额外的网络请求或动态内容加载。\n3. **使用开发者工具**:手动查看页面元素和结构。\n\n如果需要进一步操作请告诉我",
"extraInfo": {
"created": 1747379151760,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
}
],
"settings": {
"modelIndex": 7,
"enableTools": [
{
"name": "k_navigate",
"description": "Navigate to a URL",
"enabled": true
},
{
"name": "k_screenshot",
"description": "Take a screenshot of the current page or a specific element",
"enabled": true
},
{
"name": "k_click",
"description": "Click an element on the page",
"enabled": true
},
{
"name": "k_fill",
"description": "Fill out an input field",
"enabled": true
},
{
"name": "k_select",
"description": "Select an element on the page with Select tag",
"enabled": true
},
{
"name": "k_hover",
"description": "Hover an element on the page",
"enabled": true
},
{
"name": "k_evaluate",
"description": "Execute JavaScript in the browser console",
"enabled": true
}
],
"enableWebSearch": false,
"temperature": 0.7,
"contextLength": 20,
"systemPrompt": ""
}
}
},
{
"name": "工具",
"icon": "icon-tool",
"type": "tool",
"componentIndex": 2,
"storage": {
"currentToolName": "k_navigate",
"formData": {
"url": "https://asoca.grand-challenge.org/",
"launchOptions": {
"headless": false
},
"allowDangerous": false
},
"lastToolCallResponse": {
"content": [
{
"type": "text",
"text": "Navigated to https://asoca.grand-challenge.org/"
}
],
"isError": false
}
}
},
{
"name": "资源",
"icon": "icon-file",
"type": "blank",
"componentIndex": 0,
"storage": {
"formData": {},
"currentType": "resource",
"currentResourceName": "Browser console logs",
"lastResourceReadResponse": {
"contents": [
{
"uri": "console://logs",
"mimeType": "text/plain",
"text": "[warn] Error with Permissions-Policy header: Unrecognized feature: 'ambient-light-sensor'.\n[warn] Error with Permissions-Policy header: Unrecognized feature: 'document-domain'.\n[warn] Error with Permissions-Policy header: Unrecognized feature: 'ambient-light-sensor'.\n[warn] Error with Permissions-Policy header: Unrecognized feature: 'document-domain'.\n[error] Failed to load resource: the server responded with a status of 403 ()\n[error] Failed to load resource: the server responded with a status of 403 ()"
}
]
}
}
},
{
"name": "工具",
"icon": "icon-tool",
"type": "blank",
"componentIndex": 2,
"storage": {
"formData": {
"script": "document.body.innerText"
},
"currentToolName": "k_evaluate",
"lastToolCallResponse": {
"content": [
{
"type": "text",
"text": "Execution result:\n\"\"\n\nConsole output:\n"
}
],
"isError": false
}
}
}
]
}

View File

@ -14,6 +14,7 @@ vsc-extension-quickstart.md
**/.vscode-test.* **/.vscode-test.*
renderer/** renderer/**
service/** service/**
news/**
test/** test/**
servers/** servers/**
scripts/** scripts/**
@ -24,3 +25,8 @@ software/**
.editorconfig .editorconfig
.gitattributes .gitattributes
*.vsix *.vsix
.turbo
.github
webpack
.openmcp
.vscode

View File

@ -1,9 +1,80 @@
# Change Log # Change Log
## [main] 0.0.9 ## [main] 0.1.10
- 修复 0.0.8 引入的 bugsystem prompt 返回的是索引而非真实内容 - 修复 issue #48: 修复错误的引导路径。
- 修复非中文语言下,初始化引导界面不跟随主界面的问题。
## [main] 0.1.9
- 增加 mook 功能可以利用随机种子或者AI生成来自动化填充测试 tool 的表单数据
- 增加工具自检功能openmcp 的 tool 下可以点击「工具模块」 右侧的 「工具自检」进入自检模式,该模式下,用户可以自己定义工具执行的拓扑顺序,然后一次性进行自动检测。
- 修复 issue #44: 完成链接跳转的平台适配
- 修复 issue #36: 完成非文件夹打开下的成功启动
- 修复 issue #45: 数组类型参数不支持
- 修复多行对话粘贴进入对话框样式异常的问题
## [main] 0.1.8
- 增加 STDIO 下的热更新,现在用户修改 mcp 代码openmcp 会自动完成一切相关功能的热更新,无需用户手动重启。
- 完成 mcpconfig.json 的导出功能,导出的 配置文件 可以通过 openmcp-sdk 框架完成低代码 agent 部署;也可以直接载入 Claude Desktop 等等 MCP 客户端中,实现 MCP 的快速部署和使用。
- 修复若干 vscode 插件端 bug
## [main] 0.1.7
- 新的构建系统
- 修复无法在 trae & cursor 中使用的 bug
## [main] 0.1.6
- 针对 0.1.5 无法在 Windows 启动的紧急修复。
- 修复环境变量中添加 token 失效的问题。
- 优化工具展示的页面布局。
## [main] 0.1.5
- 修复 gemini 获取模型列表时存在 models 前缀的问题
- 增加 web api 功能
- 修复无法在对话框中使用 mcp resource 的 bug
- 调试结果的工作区从 .vscode 迁移到 .openmcp连接配置文件从 .vscode/openmcp_connection.json 迁移到 .openmcp/connection.json
- 技术栈更新openmcp 全链路组件切换为 esm
- 优化引导界面文字布局。
- 文档和软件本体支持其他国家语言。
## [main] 0.1.4
- 重新实现 openai 协议的底层网络实现,从而支持 Google Gemini 全系列模型。
- 实现 index 适配器,从而支持 Grok3 全系列模型。
- 解决 issue#23 插件创建连接时报错“Cannot read properties of undefined (reading 'name')”
- 在填写 apikey 和 baseurl 的情况下,现在可以一键刷新模型列表,避免用户手动输入模型列表。
## [main] 0.1.3
- 解决 issue#21 点击按钮后的发送文本后不会清空当前的输入框。
- 修复暂停按键在多轮对话后消失的问题。
- 修复 issue#25 无法连接 streamable http 的问题。
## [main] 0.1.2
- 新特性:用户发送的信息增加「重新发送」按钮。
- 支持特性 issue#17 「关于左侧添加mcp服务器操作优化问题」增加强制聚焦功能用户创建mcp服务器连接的过程中不会让输入框失去焦点。
- 更新 MCP & OpenAI 协议内容。
- 解决 issue#21 vscode插件界面bug在高度有限情况下无法通过滚动完全显示连接按钮。
- 解决 issue#21 最后一个标签页关闭并恢复默认页面。
- 解决 issue#22 工具模块UI异常现在 openmcp 支持解析 pydantic 进行 typing 的 python mcp 了。
- 优化对象输入框,现在对象输入框具有语法高亮和受限度的自动补全了。
- 对于 trae 的所有默认主题进行额外支持。
## [main] 0.1.1
- 修复 SSH 连接 Ubuntu 的情况下的部分 bug
- 修复 python 项目点击 openmcp 进行连接时,初始化参数错误的问题
- 取消 service 底层的 mcp 连接复用技术,防止无法刷新
- 修复连接后,可能无法在欢迎界面选择调试选项的 bug
## [main] 0.1.0
- 新特性:支持同时连入多个 mcp server - 新特性:支持同时连入多个 mcp server
- 新特性:更新协议内容,支持 streamable http 协议,未来将逐步取代 SSE 的连接方式 - 新特性:更新协议内容,支持 streamable http 协议,未来将逐步取代 SSE 的连接方式
- impl issue#16:对于 uv 创建的 py 项目进行特殊支持,自动初始化项目,并将 mcp 定向到 .venv/bin/mcp 中,不再需要用户全局安装 mcp
- 对于 npm 创建的 js/ts 项目进行特殊支持:自动初始化项目
- 去除了 websearch 的设置,增加了 parallel_tool_calls 的设置parallel_tool_calls 默认为 true代表 允许模型在单轮回复中调用多个工具
- 重构了 openmcp 连接模块的基础设施,基于新的技术设施实现了更加详细的连接模块的日志系统.
- impl issue#15:无法复制
- impl issue#14:增加日志清除按钮
## [main] 0.0.9
- 修复 0.0.8 引入的bugsystem prompt 返回的是索引而非真实内容
- 测试新的发布管线
## [main] 0.0.8 ## [main] 0.0.8
- 大模型 API 测试时更加完整的报错 - 大模型 API 测试时更加完整的报错

169
CLAUDE.md Normal file
View File

@ -0,0 +1,169 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Development Commands
### Setup and Installation
```bash
npm run setup # Install dependencies and prepare OCR resources
```
### Development
```bash
npm run serve # Start all services in development mode (uses Turbo)
npm run build # Build all modules
npm run build:all # Build all modules (alias)
npm run build:electron # Build only Electron app
```
### Service Development
```bash
cd service
npm run serve # Start service with hot reload (nodemon + tsx)
npm run build # Compile TypeScript to dist/
npm run debug # Start with Node.js inspector
npm run typecheck # Type checking without emit
```
### Renderer Development
```bash
cd renderer
npm run serve # Start Vite dev server
npm run build # Build for production
npm run serve:website # Start in website mode
npm run type-check # Vue TypeScript checking
```
### VSCode Extension
```bash
npm run vscode:prepublish # Prepare for VSCode publishing (Rollup build)
npm run compile # Compile TypeScript
npm run watch # Watch mode compilation
vsce package # Create VSIX package for distribution
vsce publish # Publish to VSCode Marketplace (requires auth)
```
### Quality Assurance
```bash
npm run lint # ESLint for TypeScript files
npm run pretest # Run compile and lint
npm run test # Run tests
```
## Architecture Overview
### Multi-Module Structure
OpenMCP follows a **layered modular architecture** with three main deployment targets:
1. **VSCode Extension** (`src/extension.ts`) - IDE integration
2. **Service Layer** (`service/`) - Node.js backend handling MCP protocol
3. **Renderer Layer** (`renderer/`) - Vue.js frontend for UI
### Key Architectural Patterns
#### Message Bridge Communication
The system uses a **message bridge pattern** for cross-platform communication:
- **VSCode**: Uses `vscode.postMessage` API
- **Electron**: Uses IPC communication
- **Web**: Uses WebSocket connections
- **Node.js**: Uses EventEmitter for SDK mode
All communication flows through `MessageBridge` class in `renderer/src/api/message-bridge.ts`.
#### MCP Client Management
- **Connection Management**: `service/src/mcp/connect.service.ts` handles multiple MCP server connections
- **Client Pooling**: `clientMap` maintains active MCP client instances with UUID-based identification
- **Transport Abstraction**: Supports STDIO, SSE, and StreamableHTTP transports
- **Auto-reconnection**: `McpServerConnectMonitor` handles connection monitoring
#### Request/Response Flow
```
Frontend (Vue) → MessageBridge → Service Router → MCP Controllers → MCP SDK → External MCP Servers
```
### Important Service Patterns
#### Preprocessing Commands
`service/src/mcp/connect.service.ts` includes **automatic environment setup**:
- Python projects: Auto-runs `uv sync` and installs MCP CLI
- Node.js projects: Auto-runs `npm install` if node_modules missing
- Path resolution: Handles `~/` home directory expansion
#### OCR Integration
Built-in OCR using Tesseract.js:
- Images from MCP responses are automatically processed
- Base64 images saved to temp files and queued for OCR
- Results delivered via worker threads
### Frontend Architecture (Vue 3)
#### State Management
- **Panel System**: Tab-based interface in `renderer/src/components/main-panel/`
- **Reactive Connections**: MCP connection state managed reactively
- **Multi-language**: Vue i18n with support for 9 languages
#### Core Components
- **Chat Interface**: `main-panel/chat/` - LLM interaction with MCP tools
- **Tool Testing**: `main-panel/tool/` - Direct MCP tool invocation
- **Resource Browser**: `main-panel/resource/` - MCP resource exploration
- **Prompt Manager**: `main-panel/prompt/` - System prompt templates
### Build System
#### Turbo Monorepo
Uses Turbo for coordinated builds across modules:
- **Dependency ordering**: Renderer builds before Electron
- **Parallel execution**: Service and Renderer can build concurrently
- **Task caching**: Disabled for development iterations
#### Rollup Configuration
VSCode extension uses Rollup for optimal bundling:
- **ES modules**: Output as ESM format
- **External dependencies**: VSCode API marked as external
- **TypeScript**: Direct compilation without webpack
## Development Workflow
### Adding New MCP Features
1. **Service Layer**: Add controller in `service/src/mcp/`
2. **Router Registration**: Add to `ModuleControllers` in `service/src/common/router.ts`
3. **Frontend Integration**: Add API calls in `renderer/src/api/`
4. **UI Components**: Create components in `renderer/src/components/`
### Testing MCP Servers
1. **Connection**: Configure in connection panel (STDIO/SSE/HTTP)
2. **Validation**: Test tools/resources in respective panels
3. **Integration**: Verify LLM interaction in chat interface
### Packaging VSCode Extension
1. **Build Dependencies**: Run `npm run build` to build all modules
2. **Prepare Extension**: Run `npm run vscode:prepublish` to bundle extension code
3. **Create Package**: Run `vsce package` to generate `.vsix` file
4. **Install Locally**: Use `code --install-extension openmcp-x.x.x.vsix` for testing
5. **Publish**: Run `vsce publish` (requires marketplace publisher account)
### Platform-Specific Considerations
- **VSCode**: Uses webview API, limited to extension context
- **Electron**: Full desktop app capabilities, local service spawning
- **Web**: Requires external service, WebSocket limitations
- **SDK**: Embedded in other Node.js applications
## Important Files
### Configuration
- `turbo.json` - Monorepo build orchestration
- `rollup.config.js` - VSCode extension bundling
- `service/package.json` - Backend dependencies and scripts
- `renderer/package.json` - Frontend dependencies and scripts
### Core Architecture
- `src/extension.ts` - VSCode extension entry point
- `service/src/main.ts` - Service WebSocket server
- `service/src/common/router.ts` - Request routing system
- `renderer/src/api/message-bridge.ts` - Cross-platform communication
- `service/src/mcp/client.service.ts` - MCP client implementation

199
README.md
View File

@ -2,76 +2,78 @@
<img src="./icons/openmcp.png" height="200px"/> <img src="./icons/openmcp.png" height="200px"/>
<h3>OpenMCP: 一体化 MCP Server 调试器</h3> <h3>OpenMCP: All you need for MCP Development</h3>
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: #CB81DA; color: white; border-radius: .5em; text-decoration: none;">👉 加入 OpenMCP正式级技术组</a> English | [中文](./README.zh.md)
<a href="https://openmcp.kirigaya.cn" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: rgb(84, 176, 84); color: white; border-radius: .5em; text-decoration: none;">🫱 Official Documentation</a>
<a href="https://discord.gg/af5cfB9a" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: rgb(84, 176, 84); color: white; border-radius: .5em; text-decoration: none;"> 加入 OpenMCP Discord频道</a> <a href="https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: #CB81DA; color: white; border-radius: .5em; text-decoration: none;">OpenMCP QQ Group</a>
<a href="https://discord.gg/SKTZRf6NzU" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: rgb(84, 176, 84); color: white; border-radius: .5em; text-decoration: none;">OpenMCP Discord Channel</a>
</div> </div>
## OpenMCP ## OpenMCP
一款用于 MCP 服务端调试的一体化 vscode/trae/cursor 插件。 An all-in-one vscode/trae/cursor plugin for MCP server debugging.
<video src="https://github.com/user-attachments/assets/ab214d58-b77c-4bd3-8b6e-55552f4036ff" width="100%"></video> [![IMAGE ALT TEXT HERE](https://pic1.zhimg.com/80/v2-951261f789708621a2c34faa5fa6f330_1440w.png)](https://www.youtube.com/watch?v=S7igsEhcLiw)
### [👆 Full Video](https://www.youtube.com/watch?v=S7igsEhcLiw)
<video src="https://github.com/user-attachments/assets/c17a4ad7-83b4-47ff-8627-85b57ad18940" width="100%"></video> Integrated Inspector + MCP client basic functions, combining development and testing into one.
集成 Inspector + MCP 客户端基础功能,开发测试一体化。
![](./icons/openmcp.welcome.png) ![](./icons/openmcp.welcome.png)
进行资源协议、工具、Prompt 的 MCP 服务器测试。 Test mcp tools, prompts and resources with a variety of tools.
![](./icons/openmcp.resource.png) ![](./icons/openmcp.resource.png)
测试完成的工具可以放入 「交互测试」 模块之间进行大模型交互测试。 Tested tools can be placed in the "Interactive Testing" module for large model interaction testing.
![](./icons/openmcp.chatbot.png) ![](./icons/openmcp.chatbot.png)
完整的项目级管理面板,更加方便的进行项目和全局的 mcp 项目管理。 Complete project-level management panel for easier MCP project management at both project and global levels.
![](./icons/openmcp.management.png) ![](./icons/openmcp.management.png)
支持多种大模型 Supports multiple large models
![](./icons/openmcp.support.llm.png) ![](./icons/openmcp.support.llm.png)
Support XML mode and customized options for your tool selection.
![](./icons/openmcp.xml.png)
## TODO ## TODO
## 需求规划 ## Feature Roadmap
| 所在模块 | 需求内容 | 功能优先级 | 当前状态 | 修复优先级 | | Module | Feature | Priority | Status | Fix Priority |
|---------|---------|--------|---------|-----------| |---------|---------|--------|---------|-----------|
| `all` | 完成最基本的各类基础设施 | `完整版本` | 100% | `Done` | | `all` | Complete basic infrastructure | `Full Version` | 100% | `Done` |
| `render` | chat 模式下支持进行成本分析 | `迭代版本` | 100% | `Done` | | `render` | Support cost analysis in chat mode | `Iteration` | 100% | `Done` |
| `ext` | 支持基本的 MCP 项目管理 | `迭代版本` | 100% | `P0` | | `ext` | Support basic MCP project management | `Iteration` | 100% | `P0` |
| `service` | 支持自定义支持 openai 接口协议的大模型接入 | `完整版本` | 100% | `Done` | | `service` | Support custom OpenAI-compatible large model integration | `Full Version` | 100% | `Done` |
| `service` | 支持自定义接口协议的大模型接入 | `MVP` | 0% | `P1` | | `service` | Support custom protocol large model integration | `MVP` | 0% | `P1` |
| `all` | 支持同时调试多个 MCP Server | `MVP` | 0% | `P1` | | `all` | Support debugging multiple MCP Servers simultaneously | `MVP` | 100% | `P0` |
| `all` | 支持通过大模型进行在线验证 | `迭代版本` | 100% | `Done` | | `all` | Support online verification via large models | `Iteration` | 100% | `Done` |
| `all` | 支持对用户对应服务器的调试工作内容进行保存 | `迭代版本` | 100% | `Done` | | `all` | Support saving user's server debugging work | `Iteration` | 100% | `Done` |
| `render` | 高危操作权限确认 | `MVP` | 0% | `P1` | | `render` | High-risk operation permission confirmation | `MVP` | 0% | `P1` |
| `service` | 对于连接的 mcp server 进行热更新 | `MVP` | 0% | `P1` | | `service` | Hot update for connected MCP servers | `MVP` | 0% | `P1` |
| `service` | 系统配置信息云同步 | `MVP` | 0% | `P1` | | `service` | Cloud sync for system configuration | `MVP` | 0% | `P1` |
| `all` | 系统提示词管理模块 | `迭代版本` | 100% | `Done` | | `all` | System prompt management module | `Iteration` | 100% | `Done` |
| `service` | 工具 wise 的日志系统 | `MVP` | 0% | `P1` | | `service` | Tool-wise logging system | `MVP` | 0% | `P1` |
| `service` | 自带 OCR 进行字符识别 | `迭代版本` | 100% | `Done` | | `service` | MCP security checks (prevent prompt injection, etc.) | `MVP` | 0% | `P1` |
| `service` | Built-in OCR for character recognition | `Iteration` | 100% | `Done` |
## Project Concept
## 项目概念 OpenMCP adopts a layered modular design. By assembling different modules, it can be implemented in different modes on different platforms.
openmcp 采用分层模块化设计,通过组装不同的模块,可以将它实现成不同平台上的不同模式。
```mermaid ```mermaid
flowchart TD flowchart TD
subgraph OpenMCP核心组件 subgraph OpenMCP Core Components
renderer[Renderer] renderer[Renderer]
openmcpservice[OpenMCPService] openmcpservice[OpenMCPService]
end end
@ -82,85 +84,92 @@ flowchart TD
nginx[Nginx] nginx[Nginx]
end end
subgraph OpenMCP_插件["OpenMCP 插件"] subgraph OpenMCP_Plugin["OpenMCP Plugin"]
renderer renderer
openmcpservice openmcpservice
vscode[VSCode 插件代码] vscode[VSCode Plugin Code]
end end
subgraph OpenMCP_App["OpenMCP App"] subgraph OpenMCP_App["OpenMCP App"]
renderer renderer
openmcpservice openmcpservice
electron[Electron 代码] electron[Electron Code]
end end
subgraph QQ机器人["基于 OpenMCP 的 QQ 机器人"] subgraph QQBot["OpenMCP-based QQ Bot"]
lagrange[Lagrange.OneBot] lagrange[Lagrange.OneBot]
openmcpservice openmcpservice
end end
%% 依赖关系 %% Dependencies
OpenMCP_Web -->|前端渲染| renderer OpenMCP_Web -->|Frontend Rendering| renderer
OpenMCP_Web -->|后端服务| openmcpservice OpenMCP_Web -->|Backend Service| openmcpservice
OpenMCP_Web -->|反向代理| nginx OpenMCP_Web -->|Reverse Proxy| nginx
OpenMCP_插件 -->|UI 界面| renderer OpenMCP_Plugin -->|UI Interface| renderer
OpenMCP_插件 -->|核心逻辑| openmcpservice OpenMCP_Plugin -->|Core Logic| openmcpservice
OpenMCP_插件 -->|集成开发| vscode OpenMCP_Plugin -->|IDE Integration| vscode
OpenMCP_App -->|前端界面| renderer OpenMCP_App -->|Frontend UI| renderer
OpenMCP_App -->|本地服务| openmcpservice OpenMCP_App -->|Local Service| openmcpservice
OpenMCP_App -->|桌面封装| electron OpenMCP_App -->|Desktop Packaging| electron
QQ机器人 -->|协议适配| lagrange QQBot -->|Protocol Adaptation| lagrange
QQ机器人 -->|业务逻辑| openmcpservice QQBot -->|Business Logic| openmcpservice
``` ```
## Development
- renderer : Frontend UI definitions
- service : Test components for renderer , including a simple forwarding layer
- src : VSCode plugin definitions
### Renderer & Service Development
```mermaid
flowchart LR
D[renderer] <--> A[Dev Server] 
<--ws--> B[service]
B <--mcp--> m(MCP Server)
```
Project setup:
```bash
npm run setup
```
Start dev server:
```bash
npm run serve
```
### Extension Development
```mermaid
flowchart LR
D[renderer] <--> A[extention.ts] <--> B[service]
B <--mcp--> m(MCP Server)
```
Build for deployment:
```bash
npm run build
```
build vscode extension:
```bash
npm run build:plugin
```
Then just press F5, いただきます (Let's begin)
--- ---
## Dev ## CI Pipeline
- `renderer`: 前端 UI 的定义 ✅ npm run build
- `service`: 测试 `renderer` 的部分,包含一个简易的转发层 ✅ npm run build:task-loop
- `src`: vscode 插件端定义 ✅ openmcp-client UT
✅ openmcp-sdk UT
### Renderer & Service Dev ✅ vscode extension UT
```mermaid
flowchart LR
D[renderer] <--> A[Dev Server] <--ws--> B[service]
B <--mcp--> m(MCP Server)
```
配置项目
```bash
## linux
./configure.sh
## windows
./configure.ps1
```
启动 dev server
```bash
npm run dev
```
> 端口占用: 8282 (renderer) + 8081 (service)
### Extention Dev
```mermaid
flowchart LR
D[renderer] <--> A[extention.ts] <--> B[service]
B <--mcp--> m(MCP Server)
```
负载部署
```bash
npm run build
```
and just press f5, いただきます

166
README.zh.md Normal file
View File

@ -0,0 +1,166 @@
<div align="center">
<img src="./icons/openmcp.png" height="200px"/>
<h3>OpenMCP: 一体化 MCP Server 调试器</h3>
[English](./README.md) | 中文
<a href="https://openmcp.kirigaya.cn" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: rgb(84, 176, 84); color: white; border-radius: .5em; text-decoration: none;"> 🫱 官方文档</a>
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: #CB81DA; color: white; border-radius: .5em; text-decoration: none;">OpenMCP QQ 讨论群</a>
<a href="https://discord.gg/SKTZRf6NzU" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: rgb(84, 176, 84); color: white; border-radius: .5em; text-decoration: none;">OpenMCP Discord 频道</a>
</div>
## OpenMCP
一款用于 MCP 服务端调试的一体化 vscode/trae/cursor 插件。
[![IMAGE ALT TEXT HERE](https://pic1.zhimg.com/80/v2-951261f789708621a2c34faa5fa6f330_1440w.png)](https://www.bilibili.com/video/BV1MFTBzpEtZ/?vd_source=3f248073d6ebdb61308992901b606f24)
### [👆 完整视频](https://www.youtube.com/watch?v=S7igsEhcLiw)
集成 Inspector + MCP 客户端基础功能,开发测试一体化。
![](./icons/openmcp.welcome.png)
进行资源协议、工具、Prompt 的 MCP 服务器测试。
![](./icons/openmcp.resource.png)
测试完成的工具可以放入 「交互测试」 模块之间进行大模型交互测试。
![](./icons/openmcp.chatbot.png)
完整的项目级管理面板,更加方便的进行项目和全局的 mcp 项目管理。
![](./icons/openmcp.management.png)
支持多种大模型
![](./icons/openmcp.support.llm.png)
支持 XML 模式和自定义工具选择
![](./icons/openmcp.xml.png)
## TODO
## 需求规划
| 所在模块 | 需求内容 | 功能优先级 | 当前状态 | 修复优先级 |
|---------|---------|--------|---------|-----------|
| `all` | 完成最基本的各类基础设施 | `完整版本` | 100% | `Done` |
| `render` | chat 模式下支持进行成本分析 | `迭代版本` | 100% | `Done` |
| `ext` | 支持基本的 MCP 项目管理 | `迭代版本` | 100% | `P0` |
| `service` | 支持自定义支持 openai 接口协议的大模型接入 | `完整版本` | 100% | `Done` |
| `service` | 支持自定义接口协议的大模型接入 | `MVP` | 0% | `P1` |
| `all` | 支持同时调试多个 MCP Server | `MVP` | 100% | `P0` |
| `all` | 支持通过大模型进行在线验证 | `迭代版本` | 100% | `Done` |
| `all` | 支持对用户对应服务器的调试工作内容进行保存 | `迭代版本` | 100% | `Done` |
| `render` | 高危操作权限确认 | `MVP` | 0% | `P1` |
| `service` | 对于连接的 mcp server 进行热更新 | `MVP` | 0% | `P1` |
| `service` | 系统配置信息云同步 | `MVP` | 0% | `P1` |
| `all` | 系统提示词管理模块 | `迭代版本` | 100% | `Done` |
| `service` | 工具 wise 的日志系统 | `MVP` | 0% | `P1` |
| `service` | 自带 OCR 进行字符识别 | `迭代版本` | 100% | `Done` |
## 项目概念
openmcp 采用分层模块化设计,通过组装不同的模块,可以将它实现成不同平台上的不同模式。
```mermaid
flowchart TD
subgraph OpenMCP核心组件
renderer[Renderer]
openmcpservice[OpenMCPService]
end
subgraph OpenMCP_Web["OpenMCP Web"]
renderer
openmcpservice
nginx[Nginx]
end
subgraph OpenMCP_插件["OpenMCP 插件"]
renderer
openmcpservice
vscode[VSCode 插件代码]
end
subgraph OpenMCP_App["OpenMCP App"]
renderer
openmcpservice
electron[Electron 代码]
end
subgraph QQ机器人["基于 OpenMCP 的 QQ 机器人"]
lagrange[Lagrange.OneBot]
openmcpservice
end
%% 依赖关系
OpenMCP_Web -->|前端渲染| renderer
OpenMCP_Web -->|后端服务| openmcpservice
OpenMCP_Web -->|反向代理| nginx
OpenMCP_插件 -->|UI 界面| renderer
OpenMCP_插件 -->|核心逻辑| openmcpservice
OpenMCP_插件 -->|集成开发| vscode
OpenMCP_App -->|前端界面| renderer
OpenMCP_App -->|本地服务| openmcpservice
OpenMCP_App -->|桌面封装| electron
QQ机器人 -->|协议适配| lagrange
QQ机器人 -->|业务逻辑| openmcpservice
```
---
## Dev
- `renderer`: 前端 UI 的定义
- `service`: 测试 `renderer` 的部分,包含一个简易的转发层
- `src`: vscode 插件端定义
### Renderer & Service Dev
```mermaid
flowchart LR
D[renderer] <--> A[Dev Server] <--ws--> B[service]
B <--mcp--> m(MCP Server)
```
配置项目
```bash
npm run setup
```
启动 dev server
```bash
npm run dev
```
> 端口占用: 8282 (renderer) + 8081 (service)
### Extention Dev
```mermaid
flowchart LR
D[renderer] <--> A[extention.ts] <--> B[service]
B <--mcp--> m(MCP Server)
```
负载部署
```bash
npm run build
```
and just press f5, いただきます

View File

@ -1,38 +0,0 @@
# 创建并清理资源目录
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"

View File

@ -1,18 +0,0 @@
#!/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"

View File

@ -1,19 +0,0 @@
## 安装 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
npm run prepare:ocr

View File

@ -1,3 +0,0 @@
cd servers && uv sync && cd ..
npm i
npm run prepare:ocr

View File

@ -1,7 +0,0 @@
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
View File

@ -1,9 +0,0 @@
#!/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"

20
esbuild.config.js Normal file
View File

@ -0,0 +1,20 @@
// esbuild.config.js
const { build } = require('esbuild');
build({
entryPoints: ['src/extension.ts'],
bundle: true,
platform: 'node',
format: 'cjs',
outfile: 'dist/extension.cjs.js',
sourcemap: true,
external: ['vscode'],
target: ['node22'],
loader: {
'.json': 'json'
},
define: { 'import.meta.url': '_importMetaUrl' },
banner: {
js: "const _importMetaUrl=require('url').pathToFileURL(__filename)",
},
}).catch(() => process.exit(1));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 KiB

After

Width:  |  Height:  |  Size: 453 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 496 KiB

After

Width:  |  Height:  |  Size: 675 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 492 KiB

After

Width:  |  Height:  |  Size: 564 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 435 KiB

After

Width:  |  Height:  |  Size: 446 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 KiB

After

Width:  |  Height:  |  Size: 187 KiB

BIN
icons/openmcp.xml.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 KiB

20
l10n/bundle.l10n.en.json Normal file
View File

@ -0,0 +1,20 @@
{
"ensure-delete-connection": "Are you sure you want to delete connection {0}?",
"confirm": "OK",
"choose-connection-type": "Please select the connection type",
"please-enter-connection-command": "Please enter the connection command",
"example-mcp-run": "For example: mcp run main.py",
"please-enter-cwd": "Please enter the working directory (cwd), optional",
"please-enter-cwd-placeholder": "For example: /path/to/project",
"please-enter-url": "Please enter the connection URL",
"example-as": "For example:",
"enter-optional-oauth": "Please enter the OAuth token, optional",
"quick-start": "Getting Started",
"read-document": "Read documentation",
"report-issue": "Report a problem",
"join-project": "Participate in the project",
"comment-plugin": "Comment Plugin",
"preset-env-sync": "Preset environment variables synchronized successfully",
"preset-env-sync.fail": "Failed to sync preset environment variables",
"error.notOpenWorkspace": "No workspace is currently open in VSCode. Please open a workspace (e.g., open a folder) first."
}

21
l10n/bundle.l10n.ja.json Normal file
View File

@ -0,0 +1,21 @@
{
"ensure-delete-connection": "接続 {0} を削除してもよろしいですか?",
"confirm": "確定",
"choose-connection-type": "接続タイプを選択してください",
"please-enter-connection-command": "接続コマンドを入力してください",
"example-mcp-run": "例: mcp run main.py",
"please-enter-cwd": "作業ディレクトリ (cwd) を入力してください (オプション)",
"please-enter-cwd-placeholder": "例: /path/to/project",
"please-enter-url": "接続URLを入力してください",
"example-as": "例えば:",
"enter-optional-oauth": "OAuthトークンを入力してください任意",
"quick-start": "はじめに",
"read-document": "ドキュメントを読む",
"report-issue": "問題を報告",
"join-project": "プロジェクトに参加する",
"comment-plugin": "コメントプラグイン",
"preset-env-sync": "プリセット環境変数の同期が完了しました",
"preset-env-sync.fail": "プリセット環境変数の同期に失敗しました",
"error.notOpenWorkspace": "現在、VSCode でワークスペースが開かれていません。まずワークスペース(例:フォルダーを開く)を開いてください。"
}

View File

@ -0,0 +1,20 @@
{
"ensure-delete-connection": "确定要删除连接 {0} 吗?",
"confirm": "确定",
"choose-connection-type": "请选择连接类型",
"please-enter-connection-command": "请输入连接的 command",
"example-mcp-run": "例如: mcp run main.py",
"please-enter-cwd": "请输入工作目录 (cwd),可选",
"please-enter-cwd-placeholder": "例如: /path/to/project",
"please-enter-url": "请输入连接的 URL",
"example-as": "例如:",
"enter-optional-oauth": "请输入 OAuth 令牌,可选",
"quick-start": "入门",
"read-document": "阅读文档",
"report-issue": "报告问题",
"join-project": "参与项目",
"comment-plugin": "评论插件",
"preset-env-sync": "预设环境变量同步完成",
"preset-env-sync.fail": "预设环境变量同步失败",
"error.notOpenWorkspace": "当前VScode没有打开工作区请先打开工作区例如打开文件夹"
}

31
news/.gitignore vendored Normal file
View File

@ -0,0 +1,31 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
./src/data.json

3
news/.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

183
news/README.md Normal file
View File

@ -0,0 +1,183 @@
## OpenMCP News
Change when user update extension
## Context
You are a clever bot to write SPA page to display the changelog and news of my software OpenMCP.
### Some basic information you should know
- Release is: https://github.com/LSTM-Kirigaya/openmcp-client/releases
- OpenMCP website is: https://openmcp.kirigaya.cn/
- Github: https://github.com/LSTM-Kirigaya/openmcp-client
- Discord: https://discord.com/invite/SKTZRf6NzU
- QQ: https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD
### Design framework
You should obey the following order to make the page:
1. 📣 What is news in xxx
2. 🐳 Learn more features
3. ✨ Core Features
4. ❤️ How to sponsor
5. 📚 Resources
6. 🔧 Troubleshooting
### Some design requirements
- Theme color of openmcp is #B988D1, you should use it wisely and don't use light blue as default theme color, use #B988D1 instead.
- This is a website designed to be opened in vscode, so you should make vscode oriented design, where you should use following css macro to dev css class:
```css
:root {
--font-monospace-family: var(--vscode-editor-font-family);
--font-monospace-weight: var(--vscode-editor-font-weight);
--font-monospace-size: var(--vscode-editor-font-size);
--link-foreground: var(--vscode-textLink-foreground);
--link-active: var(--vscode-textLink-activeForeground);
/* UI & Control */
--input-active-background: var(--vscode-input-background);
--input-active-border: var(--vscode-focusBorder);
--input-active-foreground: var(--vscode-input-foreground);
--input-error-background: var(--vscode-inputValidation-errorBackground);
--input-error-border: var(--vscode-inputValidation-errorBorder);
--input-error-foreground: var(--vscode-inputValidation-errorForeground);
--input-foreground: var(--vscode-input-foreground);
--input-background: var(--vscode-input-background);
--input-border: var(--vscode-input-border);
--input-hover: var(--vscode-input-background);
--input-placeholder: var(--vscode-input-placeholderForeground);
--input-radius: 0px;
--scrollbar-background: var(--vscode-scrollbarSlider-background);
--scrollbar-hover: var(--vscode-scrollbarSlider-hoverBackground);
--scrollbar-active: var(--vscode-scrollbarSlider-activeBackground);
/* Window */
--title-bar: #1f1f1f;
--title-color: #fff;
--foreground: var(--vscode-editor-foreground);
--background: var(--vscode-editor-background);
--label: rgb(189, 189, 189);
--shadow: #000;
--border: var(--vscode-input-border);
--window-button-hover: rgba(255,255,255,0.1);
--window-button-active: rgba(255,255,255,0.2);
--window-blur-background: rgba(0,0,0,0.25);
--window-title-foreground: var(--foreground);
--window-background: var(--sidebar);
--window-border: transparent;
--window-radius: 0px;
/* Sidebar */
--sidebar: var(--vscode-sideBar-background);
--sidebar-border: var(--vscode-sideBar-border);
--sidebar-min-width: 280px;
--sidebar-item-text: var(--vscode-list-inactiveSelectionForeground);
--sidebar-item-border: var(--vscode-input-border);
--sidebar-item-background: var(--sidebar);
--sidebar-item-selected: var(--vscode-list-inactiveSelectionBackground);
--sidebar-item-hover: var(--vscode-list-hoverBackground);
--sidebar-item-max-height: 40px;
--sidebar-item-radix-background: var(--vscode-breadcrumb-background);
--sidebar-group-text: var(--vscode-sideBarSectionHeader-foreground);
--sidebar-group-border: var(--vscode-sideBarSectionHeader-border);
--sidebar-group-background: var(--vscode-sideBarSectionHeader-background);
/* Labels */
--signalSize-background: rgba(0,0,0,0.5);
--signalSize-border: rgba(255,255,255,0.2);
--signalSize-color: var(--foreground);
/* Color Picker */
--picker-swatch-size: 15px;
--picker-swatch-cols: 8;
--picker-background: var(--vscode-breadcrumbPicker-background);
--picker-border: var(--vscode-dropdown-border);
/* Search */
--search-background: var(--vscode-quickInput-background);
--search-border: var(--border);
--search-panel-background: transparent;
--search-panel-border: var(--vscode-pickerGroup-border);
--search-panel-text: var(--vscode-quickInput-foreground);
--search-label: var(--foreground);
--search-selected-background: var(--vscode-list-inactiveSelectionBackground);
/* Properties */
--properties-background: var(--vscode-breadcrumb-background);
--properties-border: var(--border);
/* Navbar */
--navBar-background: var(--sidebar);
--navBar-height: 32px;
--navBar-button: transparent;
--navBar-button-text: var(--foreground);
--navBar-group-background: var(--background);
--navBar-preview-background: var(--vscode-scrollbarSlider-background);
--navBar-slider-border: var(--foreground);
/* Buttons */
--button: var(--vscode-button-background);
--button-text: var(--vscode-button-foreground);
--button-hover: var(--vscode-button-hoverBackground);
--button-active: var(--vscode-button-hoverBackground);
--button-disabled: var(--vscode-activityBar-background);
--button-disabled-text: var(--vscode-activityBar-inactiveForeground);
/* Grid Lines */
--grid-dash: 2;
--grid-space: 4;
--grid-line: var(--vscode-editorIndentGuide-background);
--grid-tick: var(--vscode-editorIndentGuide-activeBackground);
/* Cursor */
--cursor: var(--vscode-editorCursor-foreground);
--cursor-ghost: rgba(255, 255, 255, 0.2);
--cursor-width: 2;
/* X-Axis */
--axis-height: 38px;
--axis-line: var(--border);
--axis-background: var(--vscode-sideBar-background);
--axis-foreground: var(--foreground);
/* Signals */
--signal-highlight: var(--vscode-list-inactiveSelectionBackground);
/* Colors */
--accent: var(--vscode-button-background);
--accent-dim: #234175;
--accent-bright: #24c5f7;
--accent-hover: var(--vscode-button-hoverBackground);
--color-red: #ff5252;
--color-pink: #ff4081;
--color-purple: #e040fb;
--color-deepPurple: #7c4dff;
--color-indigo: #536dfe;
--color-blue: #448aff;
--color-lightBlue: #40c4ff;
--color-cyan: #18ffff;
--color-teal: #64ffda;
--color-green: #69f0ae;
--color-lightGreen: #b2ff59;
--color-lime: #eeff41;
--color-yellow: #ffff00;
--color-amber: #ffd740;
--color-orange: #ffab40;
--color-deepOrange: #ff6e40;
/* Settings */
--settings-action-background: var(--background);
}
```

6
news/env.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

24
news/index.html Normal file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/default-dark.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenMCP News Feature</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
<style>
.openmcp-icon {
display: inline-block;
vertical-align: middle;
background: url('https://openmcp.kirigaya.cn/images/favicon.svg') no-repeat center/contain;
}
</style>

File diff suppressed because it is too large Load Diff

29
news/package.json Normal file
View File

@ -0,0 +1,29 @@
{
"name": "news",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build"
},
"dependencies": {
"vanilla-tilt": "^1.8.1",
"vue": "^3.5.17"
},
"devDependencies": {
"@tsconfig/node22": "^22.0.2",
"@types/node": "^22.15.32",
"@vitejs/plugin-vue": "^6.0.0",
"@vue/tsconfig": "^0.7.0",
"npm-run-all2": "^8.0.4",
"typescript": "~5.8.0",
"vite": "^7.0.0",
"vite-plugin-singlefile": "^2.3.0",
"vite-plugin-vue-devtools": "^7.7.7",
"vue-tsc": "^2.2.10"
}
}

View File

@ -0,0 +1,772 @@
:root {
--vscode-foreground: #cccccc;
--vscode-disabledForeground: rgba(204, 204, 204, 0.5);
--vscode-errorForeground: #f48771;
--vscode-descriptionForeground: rgba(204, 204, 204, 0.7);
--vscode-icon-foreground: #c5c5c5;
--vscode-focusBorder: #007fd4;
--vscode-textLink-foreground: #3794ff;
--vscode-textLink-activeForeground: #3794ff;
--vscode-textSeparator-foreground: rgba(255, 255, 255, 0.18);
--vscode-textPreformat-foreground: #d7ba7d;
--vscode-textPreformat-background: rgba(255, 255, 255, 0.1);
--vscode-textBlockQuote-background: #222222;
--vscode-textBlockQuote-border: rgba(0, 122, 204, 0.5);
--vscode-textCodeBlock-background: rgba(10, 10, 10, 0.4);
--vscode-sash-hoverBorder: #007fd4;
--vscode-badge-background: #4d4d4d;
--vscode-badge-foreground: #ffffff;
--vscode-scrollbar-shadow: #000000;
--vscode-scrollbarSlider-background: rgba(121, 121, 121, 0.4);
--vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7);
--vscode-scrollbarSlider-activeBackground: rgba(191, 191, 191, 0.4);
--vscode-progressBar-background: #0e70c0;
--vscode-editor-background: #1e1e1e;
--vscode-editor-foreground: #d4d4d4;
--vscode-editorStickyScroll-background: #1e1e1e;
--vscode-editorStickyScrollHover-background: #2a2d2e;
--vscode-editorStickyScroll-shadow: #000000;
--vscode-editorWidget-background: #252526;
--vscode-editorWidget-foreground: #cccccc;
--vscode-editorWidget-border: #454545;
--vscode-editorError-foreground: #f14c4c;
--vscode-editorWarning-foreground: #cca700;
--vscode-editorInfo-foreground: #3794ff;
--vscode-editorHint-foreground: rgba(238, 238, 238, 0.7);
--vscode-editorLink-activeForeground: #4e94ce;
--vscode-editor-selectionBackground: #264f78;
--vscode-editor-inactiveSelectionBackground: #3a3d41;
--vscode-editor-selectionHighlightBackground: rgba(173, 214, 255, 0.15);
--vscode-editor-findMatchBackground: #515c6a;
--vscode-editor-findMatchHighlightBackground: rgba(234, 92, 0, 0.33);
--vscode-editor-findRangeHighlightBackground: rgba(58, 61, 65, 0.4);
--vscode-editor-hoverHighlightBackground: rgba(38, 79, 120, 0.25);
--vscode-editorHoverWidget-background: #252526;
--vscode-editorHoverWidget-foreground: #cccccc;
--vscode-editorHoverWidget-border: #454545;
--vscode-editorHoverWidget-statusBarBackground: #2c2c2d;
--vscode-editorInlayHint-foreground: #969696;
--vscode-editorInlayHint-background: rgba(77, 77, 77, 0.1);
--vscode-editorInlayHint-typeForeground: #969696;
--vscode-editorInlayHint-typeBackground: rgba(77, 77, 77, 0.1);
--vscode-editorInlayHint-parameterForeground: #969696;
--vscode-editorInlayHint-parameterBackground: rgba(77, 77, 77, 0.1);
--vscode-editorLightBulb-foreground: #ffcc00;
--vscode-editorLightBulbAutoFix-foreground: #75beff;
--vscode-editorLightBulbAi-foreground: #ffcc00;
--vscode-editor-snippetTabstopHighlightBackground: rgba(124, 124, 124, 0.3);
--vscode-editor-snippetFinalTabstopHighlightBorder: #525252;
--vscode-diffEditor-insertedTextBackground: rgba(156, 204, 44, 0.2);
--vscode-diffEditor-removedTextBackground: rgba(255, 0, 0, 0.2);
--vscode-diffEditor-insertedLineBackground: rgba(155, 185, 85, 0.2);
--vscode-diffEditor-removedLineBackground: rgba(255, 0, 0, 0.2);
--vscode-diffEditor-diagonalFill: rgba(204, 204, 204, 0.2);
--vscode-diffEditor-unchangedRegionBackground: #252526;
--vscode-diffEditor-unchangedRegionForeground: #cccccc;
--vscode-diffEditor-unchangedCodeBackground: rgba(116, 116, 116, 0.16);
--vscode-widget-shadow: rgba(0, 0, 0, 0.36);
--vscode-widget-border: #303031;
--vscode-toolbar-hoverBackground: rgba(90, 93, 94, 0.31);
--vscode-toolbar-activeBackground: rgba(99, 102, 103, 0.31);
--vscode-breadcrumb-foreground: rgba(204, 204, 204, 0.8);
--vscode-breadcrumb-background: #1e1e1e;
--vscode-breadcrumb-focusForeground: #e0e0e0;
--vscode-breadcrumb-activeSelectionForeground: #e0e0e0;
--vscode-breadcrumbPicker-background: #252526;
--vscode-merge-currentHeaderBackground: rgba(64, 200, 174, 0.5);
--vscode-merge-currentContentBackground: rgba(64, 200, 174, 0.2);
--vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, 0.5);
--vscode-merge-incomingContentBackground: rgba(64, 166, 255, 0.2);
--vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 0.4);
--vscode-merge-commonContentBackground: rgba(96, 96, 96, 0.16);
--vscode-editorOverviewRuler-currentContentForeground: rgba(64, 200, 174, 0.5);
--vscode-editorOverviewRuler-incomingContentForeground: rgba(64, 166, 255, 0.5);
--vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, 0.4);
--vscode-editorOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49);
--vscode-editorOverviewRuler-selectionHighlightForeground: rgba(160, 160, 160, 0.8);
--vscode-problemsErrorIcon-foreground: #f14c4c;
--vscode-problemsWarningIcon-foreground: #cca700;
--vscode-problemsInfoIcon-foreground: #3794ff;
--vscode-minimap-findMatchHighlight: #d18616;
--vscode-minimap-selectionOccurrenceHighlight: #676767;
--vscode-minimap-selectionHighlight: #264f78;
--vscode-minimap-infoHighlight: #3794ff;
--vscode-minimap-warningHighlight: #cca700;
--vscode-minimap-errorHighlight: rgba(255, 18, 18, 0.7);
--vscode-minimap-foregroundOpacity: #000000;
--vscode-minimapSlider-background: rgba(121, 121, 121, 0.2);
--vscode-minimapSlider-hoverBackground: rgba(100, 100, 100, 0.35);
--vscode-minimapSlider-activeBackground: rgba(191, 191, 191, 0.2);
--vscode-charts-foreground: #cccccc;
--vscode-charts-lines: rgba(204, 204, 204, 0.5);
--vscode-charts-red: #f14c4c;
--vscode-charts-blue: #3794ff;
--vscode-charts-yellow: #cca700;
--vscode-charts-orange: #d18616;
--vscode-charts-green: #89d185;
--vscode-charts-purple: #b180d7;
--vscode-input-background: #3c3c3c;
--vscode-input-foreground: #cccccc;
--vscode-inputOption-activeBorder: #007acc;
--vscode-inputOption-hoverBackground: rgba(90, 93, 94, 0.5);
--vscode-inputOption-activeBackground: rgba(0, 127, 212, 0.4);
--vscode-inputOption-activeForeground: #ffffff;
--vscode-input-placeholderForeground: #a6a6a6;
--vscode-inputValidation-infoBackground: #063b49;
--vscode-inputValidation-infoBorder: #007acc;
--vscode-inputValidation-warningBackground: #352a05;
--vscode-inputValidation-warningBorder: #b89500;
--vscode-inputValidation-errorBackground: #5a1d1d;
--vscode-inputValidation-errorBorder: #be1100;
--vscode-dropdown-background: #3c3c3c;
--vscode-dropdown-foreground: #f0f0f0;
--vscode-dropdown-border: #3c3c3c;
--vscode-button-foreground: #ffffff;
--vscode-button-separator: rgba(255, 255, 255, 0.4);
--vscode-button-background: #0e639c;
--vscode-button-hoverBackground: #1177bb;
--vscode-button-secondaryForeground: #ffffff;
--vscode-button-secondaryBackground: #3a3d41;
--vscode-button-secondaryHoverBackground: #45494e;
--vscode-radio-activeForeground: #ffffff;
--vscode-radio-activeBackground: rgba(0, 127, 212, 0.4);
--vscode-radio-activeBorder: #007acc;
--vscode-radio-inactiveBorder: rgba(255, 255, 255, 0.2);
--vscode-radio-inactiveHoverBackground: rgba(90, 93, 94, 0.5);
--vscode-checkbox-background: #3c3c3c;
--vscode-checkbox-selectBackground: #252526;
--vscode-checkbox-foreground: #f0f0f0;
--vscode-checkbox-border: #6b6b6b;
--vscode-checkbox-selectBorder: #c5c5c5;
--vscode-keybindingLabel-background: rgba(128, 128, 128, 0.17);
--vscode-keybindingLabel-foreground: #cccccc;
--vscode-keybindingLabel-border: rgba(51, 51, 51, 0.6);
--vscode-keybindingLabel-bottomBorder: rgba(68, 68, 68, 0.6);
--vscode-list-focusOutline: #007fd4;
--vscode-list-activeSelectionBackground: #04395e;
--vscode-list-activeSelectionForeground: #ffffff;
--vscode-list-activeSelectionIconForeground: #ffffff;
--vscode-list-inactiveSelectionBackground: #37373d;
--vscode-list-hoverBackground: #2a2d2e;
--vscode-list-dropBackground: #383b3d;
--vscode-list-dropBetweenBackground: #c5c5c5;
--vscode-list-highlightForeground: #2aaaff;
--vscode-list-focusHighlightForeground: #2aaaff;
--vscode-list-invalidItemForeground: #b89500;
--vscode-list-errorForeground: #f88070;
--vscode-list-warningForeground: #cca700;
--vscode-listFilterWidget-background: #252526;
--vscode-listFilterWidget-outline: rgba(0, 0, 0, 0);
--vscode-listFilterWidget-noMatchesOutline: #be1100;
--vscode-listFilterWidget-shadow: rgba(0, 0, 0, 0.36);
--vscode-list-filterMatchBackground: rgba(234, 92, 0, 0.33);
--vscode-list-deemphasizedForeground: #8c8c8c;
--vscode-tree-indentGuidesStroke: #585858;
--vscode-tree-inactiveIndentGuidesStroke: rgba(88, 88, 88, 0.4);
--vscode-tree-tableColumnsBorder: rgba(204, 204, 204, 0.13);
--vscode-tree-tableOddRowsBackground: rgba(204, 204, 204, 0.04);
--vscode-editorActionList-background: #252526;
--vscode-editorActionList-foreground: #cccccc;
--vscode-editorActionList-focusForeground: #ffffff;
--vscode-editorActionList-focusBackground: #04395e;
--vscode-menu-border: #454545;
--vscode-menu-foreground: #cccccc;
--vscode-menu-background: #252526;
--vscode-menu-selectionForeground: #ffffff;
--vscode-menu-selectionBackground: #0078d4;
--vscode-menu-separatorBackground: #454545;
--vscode-quickInput-background: #252526;
--vscode-quickInput-foreground: #cccccc;
--vscode-quickInputTitle-background: rgba(255, 255, 255, 0.1);
--vscode-pickerGroup-foreground: #3794ff;
--vscode-pickerGroup-border: #3f3f46;
--vscode-quickInputList-focusForeground: #ffffff;
--vscode-quickInputList-focusIconForeground: #ffffff;
--vscode-quickInputList-focusBackground: #04395e;
--vscode-search-resultsInfoForeground: rgba(204, 204, 204, 0.65);
--vscode-searchEditor-findMatchBackground: rgba(234, 92, 0, 0.22);
--vscode-editor-lineHighlightBorder: #282828;
--vscode-editor-rangeHighlightBackground: rgba(255, 255, 255, 0.04);
--vscode-editor-symbolHighlightBackground: rgba(234, 92, 0, 0.33);
--vscode-editorCursor-foreground: #aeafad;
--vscode-editorMultiCursor-primary-foreground: #aeafad;
--vscode-editorMultiCursor-secondary-foreground: #aeafad;
--vscode-editorWhitespace-foreground: rgba(227, 228, 226, 0.16);
--vscode-editorLineNumber-foreground: #858585;
--vscode-editorIndentGuide-background: rgba(227, 228, 226, 0.16);
--vscode-editorIndentGuide-activeBackground: rgba(227, 228, 226, 0.16);
--vscode-editorIndentGuide-background1: #404040;
--vscode-editorIndentGuide-background2: rgba(0, 0, 0, 0);
--vscode-editorIndentGuide-background3: rgba(0, 0, 0, 0);
--vscode-editorIndentGuide-background4: rgba(0, 0, 0, 0);
--vscode-editorIndentGuide-background5: rgba(0, 0, 0, 0);
--vscode-editorIndentGuide-background6: rgba(0, 0, 0, 0);
--vscode-editorIndentGuide-activeBackground1: #707070;
--vscode-editorIndentGuide-activeBackground2: rgba(0, 0, 0, 0);
--vscode-editorIndentGuide-activeBackground3: rgba(0, 0, 0, 0);
--vscode-editorIndentGuide-activeBackground4: rgba(0, 0, 0, 0);
--vscode-editorIndentGuide-activeBackground5: rgba(0, 0, 0, 0);
--vscode-editorIndentGuide-activeBackground6: rgba(0, 0, 0, 0);
--vscode-editorActiveLineNumber-foreground: #c6c6c6;
--vscode-editorLineNumber-activeForeground: #c6c6c6;
--vscode-editorRuler-foreground: #5a5a5a;
--vscode-editorCodeLens-foreground: #999999;
--vscode-editorBracketMatch-background: rgba(0, 100, 0, 0.1);
--vscode-editorBracketMatch-border: #888888;
--vscode-editorOverviewRuler-border: rgba(127, 127, 127, 0.3);
--vscode-editorGutter-background: #1e1e1e;
--vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.67);
--vscode-editorGhostText-foreground: rgba(255, 255, 255, 0.34);
--vscode-editorOverviewRuler-rangeHighlightForeground: rgba(0, 122, 204, 0.6);
--vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, 0.7);
--vscode-editorOverviewRuler-warningForeground: #cca700;
--vscode-editorOverviewRuler-infoForeground: #3794ff;
--vscode-editorBracketHighlight-foreground1: #ffd700;
--vscode-editorBracketHighlight-foreground2: #da70d6;
--vscode-editorBracketHighlight-foreground3: #179fff;
--vscode-editorBracketHighlight-foreground4: rgba(0, 0, 0, 0);
--vscode-editorBracketHighlight-foreground5: rgba(0, 0, 0, 0);
--vscode-editorBracketHighlight-foreground6: rgba(0, 0, 0, 0);
--vscode-editorBracketHighlight-unexpectedBracket-foreground: rgba(255, 18, 18, 0.8);
--vscode-editorBracketPairGuide-background1: rgba(0, 0, 0, 0);
--vscode-editorBracketPairGuide-background2: rgba(0, 0, 0, 0);
--vscode-editorBracketPairGuide-background3: rgba(0, 0, 0, 0);
--vscode-editorBracketPairGuide-background4: rgba(0, 0, 0, 0);
--vscode-editorBracketPairGuide-background5: rgba(0, 0, 0, 0);
--vscode-editorBracketPairGuide-background6: rgba(0, 0, 0, 0);
--vscode-editorBracketPairGuide-activeBackground1: rgba(0, 0, 0, 0);
--vscode-editorBracketPairGuide-activeBackground2: rgba(0, 0, 0, 0);
--vscode-editorBracketPairGuide-activeBackground3: rgba(0, 0, 0, 0);
--vscode-editorBracketPairGuide-activeBackground4: rgba(0, 0, 0, 0);
--vscode-editorBracketPairGuide-activeBackground5: rgba(0, 0, 0, 0);
--vscode-editorBracketPairGuide-activeBackground6: rgba(0, 0, 0, 0);
--vscode-editorUnicodeHighlight-border: #cca700;
--vscode-diffEditor-move-border: rgba(139, 139, 139, 0.61);
--vscode-diffEditor-moveActive-border: #ffa500;
--vscode-diffEditor-unchangedRegionShadow: #000000;
--vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0;
--vscode-symbolIcon-arrayForeground: #cccccc;
--vscode-symbolIcon-booleanForeground: #cccccc;
--vscode-symbolIcon-classForeground: #ee9d28;
--vscode-symbolIcon-colorForeground: #cccccc;
--vscode-symbolIcon-constantForeground: #cccccc;
--vscode-symbolIcon-constructorForeground: #b180d7;
--vscode-symbolIcon-enumeratorForeground: #ee9d28;
--vscode-symbolIcon-enumeratorMemberForeground: #75beff;
--vscode-symbolIcon-eventForeground: #ee9d28;
--vscode-symbolIcon-fieldForeground: #75beff;
--vscode-symbolIcon-fileForeground: #cccccc;
--vscode-symbolIcon-folderForeground: #cccccc;
--vscode-symbolIcon-functionForeground: #b180d7;
--vscode-symbolIcon-interfaceForeground: #75beff;
--vscode-symbolIcon-keyForeground: #cccccc;
--vscode-symbolIcon-keywordForeground: #cccccc;
--vscode-symbolIcon-methodForeground: #b180d7;
--vscode-symbolIcon-moduleForeground: #cccccc;
--vscode-symbolIcon-namespaceForeground: #cccccc;
--vscode-symbolIcon-nullForeground: #cccccc;
--vscode-symbolIcon-numberForeground: #cccccc;
--vscode-symbolIcon-objectForeground: #cccccc;
--vscode-symbolIcon-operatorForeground: #cccccc;
--vscode-symbolIcon-packageForeground: #cccccc;
--vscode-symbolIcon-propertyForeground: #cccccc;
--vscode-symbolIcon-referenceForeground: #cccccc;
--vscode-symbolIcon-snippetForeground: #cccccc;
--vscode-symbolIcon-stringForeground: #cccccc;
--vscode-symbolIcon-structForeground: #cccccc;
--vscode-symbolIcon-textForeground: #cccccc;
--vscode-symbolIcon-typeParameterForeground: #cccccc;
--vscode-symbolIcon-unitForeground: #cccccc;
--vscode-symbolIcon-variableForeground: #75beff;
--vscode-actionBar-toggledBackground: #383a49;
--vscode-peekViewTitle-background: #252526;
--vscode-peekViewTitleLabel-foreground: #ffffff;
--vscode-peekViewTitleDescription-foreground: rgba(204, 204, 204, 0.7);
--vscode-peekView-border: #3794ff;
--vscode-peekViewResult-background: #252526;
--vscode-peekViewResult-lineForeground: #bbbbbb;
--vscode-peekViewResult-fileForeground: #ffffff;
--vscode-peekViewResult-selectionBackground: rgba(51, 153, 255, 0.2);
--vscode-peekViewResult-selectionForeground: #ffffff;
--vscode-peekViewEditor-background: #001f33;
--vscode-peekViewEditorGutter-background: #001f33;
--vscode-peekViewEditorStickyScroll-background: #001f33;
--vscode-peekViewResult-matchHighlightBackground: rgba(234, 92, 0, 0.3);
--vscode-peekViewEditor-matchHighlightBackground: rgba(255, 143, 0, 0.6);
--vscode-editor-foldBackground: rgba(38, 79, 120, 0.3);
--vscode-editor-foldPlaceholderForeground: #808080;
--vscode-editorGutter-foldingControlForeground: #c5c5c5;
--vscode-editorSuggestWidget-background: #252526;
--vscode-editorSuggestWidget-border: #454545;
--vscode-editorSuggestWidget-foreground: #d4d4d4;
--vscode-editorSuggestWidget-selectedForeground: #ffffff;
--vscode-editorSuggestWidget-selectedIconForeground: #ffffff;
--vscode-editorSuggestWidget-selectedBackground: #04395e;
--vscode-editorSuggestWidget-highlightForeground: #2aaaff;
--vscode-editorSuggestWidget-focusHighlightForeground: #2aaaff;
--vscode-editorSuggestWidgetStatus-foreground: rgba(212, 212, 212, 0.5);
--vscode-editorMarkerNavigationError-background: #f14c4c;
--vscode-editorMarkerNavigationError-headerBackground: rgba(241, 76, 76, 0.1);
--vscode-editorMarkerNavigationWarning-background: #cca700;
--vscode-editorMarkerNavigationWarning-headerBackground: rgba(204, 167, 0, 0.1);
--vscode-editorMarkerNavigationInfo-background: #3794ff;
--vscode-editorMarkerNavigationInfo-headerBackground: rgba(55, 148, 255, 0.1);
--vscode-editorMarkerNavigation-background: #1e1e1e;
--vscode-editor-linkedEditingBackground: rgba(255, 0, 0, 0.3);
--vscode-editor-wordHighlightBackground: rgba(87, 87, 87, 0.72);
--vscode-editor-wordHighlightStrongBackground: rgba(0, 73, 114, 0.72);
--vscode-editor-wordHighlightTextBackground: rgba(87, 87, 87, 0.72);
--vscode-editorOverviewRuler-wordHighlightForeground: rgba(160, 160, 160, 0.8);
--vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(192, 160, 192, 0.8);
--vscode-editorOverviewRuler-wordHighlightTextForeground: rgba(160, 160, 160, 0.8);
--vscode-editorHoverWidget-highlightForeground: #2aaaff;
--vscode-editor-placeholder-foreground: rgba(255, 255, 255, 0.34);
--vscode-tab-activeBackground: #1e1e1e;
--vscode-tab-unfocusedActiveBackground: #1e1e1e;
--vscode-tab-inactiveBackground: #2d2d2d;
--vscode-tab-unfocusedInactiveBackground: #2d2d2d;
--vscode-tab-activeForeground: #ffffff;
--vscode-tab-inactiveForeground: rgba(255, 255, 255, 0.5);
--vscode-tab-unfocusedActiveForeground: rgba(255, 255, 255, 0.5);
--vscode-tab-unfocusedInactiveForeground: rgba(255, 255, 255, 0.25);
--vscode-tab-border: #252526;
--vscode-tab-lastPinnedBorder: rgba(204, 204, 204, 0.2);
--vscode-tab-selectedBackground: #222222;
--vscode-tab-selectedForeground: rgba(255, 255, 255, 0.63);
--vscode-tab-dragAndDropBorder: #ffffff;
--vscode-tab-activeModifiedBorder: #3399cc;
--vscode-tab-inactiveModifiedBorder: rgba(51, 153, 204, 0.5);
--vscode-tab-unfocusedActiveModifiedBorder: rgba(51, 153, 204, 0.5);
--vscode-tab-unfocusedInactiveModifiedBorder: rgba(51, 153, 204, 0.25);
--vscode-editorPane-background: #1e1e1e;
--vscode-editorGroupHeader-tabsBackground: #252526;
--vscode-editorGroupHeader-noTabsBackground: #1e1e1e;
--vscode-editorGroup-border: #444444;
--vscode-editorGroup-dropBackground: rgba(83, 89, 93, 0.5);
--vscode-editorGroup-dropIntoPromptForeground: #cccccc;
--vscode-editorGroup-dropIntoPromptBackground: #252526;
--vscode-sideBySideEditor-horizontalBorder: #444444;
--vscode-sideBySideEditor-verticalBorder: #444444;
--vscode-panel-background: #1e1e1e;
--vscode-panel-border: rgba(128, 128, 128, 0.35);
--vscode-panelTitle-activeForeground: #e7e7e7;
--vscode-panelTitle-inactiveForeground: rgba(231, 231, 231, 0.6);
--vscode-panelTitle-activeBorder: #e7e7e7;
--vscode-panel-dropBorder: #e7e7e7;
--vscode-panelSection-dropBackground: rgba(83, 89, 93, 0.5);
--vscode-panelSectionHeader-background: rgba(128, 128, 128, 0.2);
--vscode-panelSection-border: rgba(128, 128, 128, 0.35);
--vscode-panelStickyScroll-background: #1e1e1e;
--vscode-panelStickyScroll-shadow: #000000;
--vscode-banner-background: #04395e;
--vscode-banner-foreground: #ffffff;
--vscode-banner-iconForeground: #3794ff;
--vscode-statusBar-foreground: #ffffff;
--vscode-statusBar-noFolderForeground: #ffffff;
--vscode-statusBar-background: #007acc;
--vscode-statusBar-noFolderBackground: #68217a;
--vscode-statusBar-focusBorder: #ffffff;
--vscode-statusBarItem-activeBackground: rgba(255, 255, 255, 0.18);
--vscode-statusBarItem-focusBorder: #ffffff;
--vscode-statusBarItem-hoverBackground: rgba(255, 255, 255, 0.12);
--vscode-statusBarItem-hoverForeground: #ffffff;
--vscode-statusBarItem-compactHoverBackground: rgba(255, 255, 255, 0.2);
--vscode-statusBarItem-prominentForeground: #ffffff;
--vscode-statusBarItem-prominentBackground: rgba(0, 0, 0, 0.5);
--vscode-statusBarItem-prominentHoverForeground: #ffffff;
--vscode-statusBarItem-prominentHoverBackground: rgba(0, 0, 0, 0.3);
--vscode-statusBarItem-errorBackground: #c72e0f;
--vscode-statusBarItem-errorForeground: #ffffff;
--vscode-statusBarItem-errorHoverForeground: #ffffff;
--vscode-statusBarItem-errorHoverBackground: rgba(255, 255, 255, 0.12);
--vscode-statusBarItem-warningBackground: #7a6400;
--vscode-statusBarItem-warningForeground: #ffffff;
--vscode-statusBarItem-warningHoverForeground: #ffffff;
--vscode-statusBarItem-warningHoverBackground: rgba(255, 255, 255, 0.12);
--vscode-activityBar-background: #333333;
--vscode-activityBar-foreground: #ffffff;
--vscode-activityBar-inactiveForeground: rgba(255, 255, 255, 0.4);
--vscode-activityBar-activeBorder: #ffffff;
--vscode-activityBar-dropBorder: #ffffff;
--vscode-activityBarBadge-background: #007acc;
--vscode-activityBarBadge-foreground: #ffffff;
--vscode-activityBarTop-foreground: #e7e7e7;
--vscode-activityBarTop-activeBorder: #e7e7e7;
--vscode-activityBarTop-inactiveForeground: rgba(231, 231, 231, 0.6);
--vscode-activityBarTop-dropBorder: #e7e7e7;
--vscode-profileBadge-background: #4d4d4d;
--vscode-profileBadge-foreground: #ffffff;
--vscode-statusBarItem-remoteBackground: #16825d;
--vscode-statusBarItem-remoteForeground: #ffffff;
--vscode-statusBarItem-remoteHoverForeground: #ffffff;
--vscode-statusBarItem-remoteHoverBackground: rgba(255, 255, 255, 0.12);
--vscode-statusBarItem-offlineBackground: #6c1717;
--vscode-statusBarItem-offlineForeground: #ffffff;
--vscode-statusBarItem-offlineHoverForeground: #ffffff;
--vscode-statusBarItem-offlineHoverBackground: rgba(255, 255, 255, 0.12);
--vscode-extensionBadge-remoteBackground: #007acc;
--vscode-extensionBadge-remoteForeground: #ffffff;
--vscode-sideBar-background: #252526;
--vscode-sideBarTitle-background: #252526;
--vscode-sideBarTitle-foreground: #bbbbbb;
--vscode-sideBar-dropBackground: rgba(83, 89, 93, 0.5);
--vscode-sideBarSectionHeader-background: rgba(0, 0, 0, 0);
--vscode-sideBarSectionHeader-border: rgba(204, 204, 204, 0.2);
--vscode-sideBarActivityBarTop-border: rgba(204, 204, 204, 0.2);
--vscode-sideBarStickyScroll-background: #252526;
--vscode-sideBarStickyScroll-shadow: #000000;
--vscode-titleBar-activeForeground: #cccccc;
--vscode-titleBar-inactiveForeground: rgba(204, 204, 204, 0.6);
--vscode-titleBar-activeBackground: #3c3c3c;
--vscode-titleBar-inactiveBackground: rgba(60, 60, 60, 0.6);
--vscode-menubar-selectionForeground: #cccccc;
--vscode-menubar-selectionBackground: rgba(90, 93, 94, 0.31);
--vscode-commandCenter-foreground: #cccccc;
--vscode-commandCenter-activeForeground: #cccccc;
--vscode-commandCenter-inactiveForeground: rgba(204, 204, 204, 0.6);
--vscode-commandCenter-background: rgba(255, 255, 255, 0.05);
--vscode-commandCenter-activeBackground: rgba(255, 255, 255, 0.08);
--vscode-commandCenter-border: rgba(204, 204, 204, 0.2);
--vscode-commandCenter-activeBorder: rgba(204, 204, 204, 0.3);
--vscode-commandCenter-inactiveBorder: rgba(204, 204, 204, 0.15);
--vscode-notificationCenter-border: #303031;
--vscode-notificationToast-border: #303031;
--vscode-notifications-foreground: #cccccc;
--vscode-notifications-background: #252526;
--vscode-notificationLink-foreground: #3794ff;
--vscode-notificationCenterHeader-background: #303031;
--vscode-notifications-border: #303031;
--vscode-notificationsErrorIcon-foreground: #f14c4c;
--vscode-notificationsWarningIcon-foreground: #cca700;
--vscode-notificationsInfoIcon-foreground: #3794ff;
--vscode-inlineChat-foreground: #cccccc;
--vscode-inlineChat-background: #252526;
--vscode-inlineChat-border: #454545;
--vscode-inlineChat-shadow: rgba(0, 0, 0, 0.36);
--vscode-inlineChatInput-border: #454545;
--vscode-inlineChatInput-focusBorder: #007fd4;
--vscode-inlineChatInput-placeholderForeground: #a6a6a6;
--vscode-inlineChatInput-background: #3c3c3c;
--vscode-inlineChatDiff-inserted: rgba(156, 204, 44, 0.1);
--vscode-editorOverviewRuler-inlineChatInserted: rgba(156, 204, 44, 0.12);
--vscode-inlineChatDiff-removed: rgba(255, 0, 0, 0.1);
--vscode-editorOverviewRuler-inlineChatRemoved: rgba(255, 0, 0, 0.12);
--vscode-extensionButton-background: #0e639c;
--vscode-extensionButton-foreground: #ffffff;
--vscode-extensionButton-hoverBackground: #1177bb;
--vscode-extensionButton-separator: rgba(255, 255, 255, 0.4);
--vscode-extensionButton-prominentBackground: #0e639c;
--vscode-extensionButton-prominentForeground: #ffffff;
--vscode-extensionButton-prominentHoverBackground: #1177bb;
--vscode-chat-requestBorder: rgba(255, 255, 255, 0.1);
--vscode-chat-requestBackground: rgba(30, 30, 30, 0.62);
--vscode-chat-slashCommandBackground: rgba(52, 65, 75, 0.56);
--vscode-chat-slashCommandForeground: #40a6ff;
--vscode-chat-avatarBackground: #1f1f1f;
--vscode-chat-avatarForeground: #cccccc;
--vscode-terminal-foreground: #cccccc;
--vscode-terminal-selectionBackground: #264f78;
--vscode-terminal-inactiveSelectionBackground: #3a3d41;
--vscode-terminalCommandDecoration-defaultBackground: rgba(255, 255, 255, 0.25);
--vscode-terminalCommandDecoration-successBackground: #1b81a8;
--vscode-terminalCommandDecoration-errorBackground: #f14c4c;
--vscode-terminalOverviewRuler-cursorForeground: rgba(160, 160, 160, 0.8);
--vscode-terminal-border: rgba(128, 128, 128, 0.35);
--vscode-terminalOverviewRuler-border: rgba(127, 127, 127, 0.3);
--vscode-terminal-findMatchBackground: #515c6a;
--vscode-terminal-hoverHighlightBackground: rgba(38, 79, 120, 0.13);
--vscode-terminal-findMatchHighlightBackground: rgba(234, 92, 0, 0.33);
--vscode-terminalOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49);
--vscode-terminal-dropBackground: rgba(83, 89, 93, 0.5);
--vscode-terminal-initialHintForeground: rgba(255, 255, 255, 0.34);
--vscode-terminalStickyScrollHover-background: #2a2d2e;
--vscode-scmGraph-historyItemRefColor: #3794ff;
--vscode-scmGraph-historyItemRemoteRefColor: #b180d7;
--vscode-scmGraph-historyItemBaseRefColor: #ea5c00;
--vscode-scmGraph-historyItemHoverDefaultLabelForeground: #cccccc;
--vscode-scmGraph-historyItemHoverDefaultLabelBackground: #4d4d4d;
--vscode-scmGraph-historyItemHoverLabelForeground: #ffffff;
--vscode-scmGraph-historyItemHoverAdditionsForeground: #81b88b;
--vscode-scmGraph-historyItemHoverDeletionsForeground: #c74e39;
--vscode-scmGraph-foreground1: #ffb000;
--vscode-scmGraph-foreground2: #dc267f;
--vscode-scmGraph-foreground3: #994f00;
--vscode-scmGraph-foreground4: #40b0a6;
--vscode-scmGraph-foreground5: #b66dff;
--vscode-commentsView-resolvedIcon: rgba(204, 204, 204, 0.5);
--vscode-commentsView-unresolvedIcon: #007fd4;
--vscode-editorCommentsWidget-replyInputBackground: #252526;
--vscode-editorCommentsWidget-resolvedBorder: rgba(204, 204, 204, 0.5);
--vscode-editorCommentsWidget-unresolvedBorder: #007fd4;
--vscode-editorCommentsWidget-rangeBackground: rgba(0, 127, 212, 0.1);
--vscode-editorCommentsWidget-rangeActiveBackground: rgba(0, 127, 212, 0.1);
--vscode-editorGutter-commentRangeForeground: #37373d;
--vscode-editorOverviewRuler-commentForeground: #37373d;
--vscode-editorOverviewRuler-commentUnresolvedForeground: #37373d;
--vscode-editorGutter-commentGlyphForeground: #d4d4d4;
--vscode-editorGutter-commentUnresolvedGlyphForeground: #d4d4d4;
--vscode-activityWarningBadge-foreground: #000000;
--vscode-activityWarningBadge-background: #cca700;
--vscode-activityErrorBadge-foreground: #000000;
--vscode-activityErrorBadge-background: #f14c4c;
--vscode-ports-iconRunningProcessForeground: #369432;
--vscode-editorWatermark-foreground: rgba(212, 212, 212, 0.6);
--vscode-settings-headerForeground: #e7e7e7;
--vscode-settings-settingsHeaderHoverForeground: rgba(231, 231, 231, 0.7);
--vscode-settings-modifiedItemIndicator: #0c7d9d;
--vscode-settings-headerBorder: rgba(128, 128, 128, 0.35);
--vscode-settings-sashBorder: rgba(128, 128, 128, 0.35);
--vscode-settings-dropdownBackground: #3c3c3c;
--vscode-settings-dropdownForeground: #f0f0f0;
--vscode-settings-dropdownBorder: #3c3c3c;
--vscode-settings-dropdownListBorder: #454545;
--vscode-settings-checkboxBackground: #3c3c3c;
--vscode-settings-checkboxForeground: #f0f0f0;
--vscode-settings-checkboxBorder: #6b6b6b;
--vscode-settings-textInputBackground: #3c3c3c;
--vscode-settings-textInputForeground: #cccccc;
--vscode-settings-numberInputBackground: #3c3c3c;
--vscode-settings-numberInputForeground: #cccccc;
--vscode-settings-focusedRowBackground: rgba(42, 45, 46, 0.6);
--vscode-settings-rowHoverBackground: rgba(42, 45, 46, 0.3);
--vscode-settings-focusedRowBorder: #007fd4;
--vscode-keybindingTable-headerBackground: rgba(204, 204, 204, 0.04);
--vscode-keybindingTable-rowsBackground: rgba(204, 204, 204, 0.04);
--vscode-debugToolBar-background: #333333;
--vscode-debugIcon-startForeground: #89d185;
--vscode-notebook-cellBorderColor: #37373d;
--vscode-notebook-focusedEditorBorder: #007fd4;
--vscode-notebookStatusSuccessIcon-foreground: #89d185;
--vscode-notebookEditorOverviewRuler-runningCellForeground: #89d185;
--vscode-notebookStatusErrorIcon-foreground: #f48771;
--vscode-notebookStatusRunningIcon-foreground: #cccccc;
--vscode-notebook-cellToolbarSeparator: rgba(128, 128, 128, 0.35);
--vscode-notebook-selectedCellBackground: #37373d;
--vscode-notebook-selectedCellBorder: #37373d;
--vscode-notebook-focusedCellBorder: #007fd4;
--vscode-notebook-inactiveFocusedCellBorder: #37373d;
--vscode-notebook-cellStatusBarItemHoverBackground: rgba(255, 255, 255, 0.15);
--vscode-notebook-cellInsertionIndicator: #007fd4;
--vscode-notebookScrollbarSlider-background: rgba(121, 121, 121, 0.4);
--vscode-notebookScrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7);
--vscode-notebookScrollbarSlider-activeBackground: rgba(191, 191, 191, 0.4);
--vscode-notebook-symbolHighlightBackground: rgba(255, 255, 255, 0.04);
--vscode-notebook-cellEditorBackground: #252526;
--vscode-notebook-editorBackground: #1e1e1e;
--vscode-debugIcon-breakpointForeground: #e51400;
--vscode-debugIcon-breakpointDisabledForeground: #848484;
--vscode-debugIcon-breakpointUnverifiedForeground: #848484;
--vscode-debugIcon-breakpointCurrentStackframeForeground: #ffcc00;
--vscode-debugIcon-breakpointStackframeForeground: #89d185;
--vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 0, 0.2);
--vscode-editor-focusedStackFrameHighlightBackground: rgba(122, 189, 122, 0.3);
--vscode-multiDiffEditor-headerBackground: #262626;
--vscode-multiDiffEditor-background: #1e1e1e;
--vscode-multiDiffEditor-border: rgba(204, 204, 204, 0.2);
--vscode-interactive-activeCodeBorder: #007acc;
--vscode-interactive-inactiveCodeBorder: #37373d;
--vscode-testing-iconFailed: #f14c4c;
--vscode-testing-iconErrored: #f14c4c;
--vscode-testing-iconPassed: #73c991;
--vscode-testing-runAction: #73c991;
--vscode-testing-iconQueued: #cca700;
--vscode-testing-iconUnset: #848484;
--vscode-testing-iconSkipped: #848484;
--vscode-testing-peekBorder: #f14c4c;
--vscode-testing-messagePeekBorder: #3794ff;
--vscode-testing-peekHeaderBackground: rgba(241, 76, 76, 0.1);
--vscode-testing-messagePeekHeaderBackground: rgba(55, 148, 255, 0.1);
--vscode-testing-coveredBackground: rgba(156, 204, 44, 0.2);
--vscode-testing-coveredBorder: rgba(156, 204, 44, 0.15);
--vscode-testing-coveredGutterBackground: rgba(156, 204, 44, 0.12);
--vscode-testing-uncoveredBranchBackground: #781212;
--vscode-testing-uncoveredBackground: rgba(255, 0, 0, 0.2);
--vscode-testing-uncoveredBorder: rgba(255, 0, 0, 0.15);
--vscode-testing-uncoveredGutterBackground: rgba(255, 0, 0, 0.3);
--vscode-testing-coverCountBadgeBackground: #4d4d4d;
--vscode-testing-coverCountBadgeForeground: #ffffff;
--vscode-testing-message-error-decorationForeground: #f14c4c;
--vscode-testing-message-error-lineBackground: rgba(255, 0, 0, 0.1);
--vscode-testing-message-info-decorationForeground: rgba(212, 212, 212, 0.5);
--vscode-testing-iconErrored-retired: rgba(241, 76, 76, 0.7);
--vscode-testing-iconFailed-retired: rgba(241, 76, 76, 0.7);
--vscode-testing-iconPassed-retired: rgba(115, 201, 145, 0.7);
--vscode-testing-iconQueued-retired: rgba(204, 167, 0, 0.7);
--vscode-testing-iconUnset-retired: rgba(132, 132, 132, 0.7);
--vscode-testing-iconSkipped-retired: rgba(132, 132, 132, 0.7);
--vscode-editorGutter-modifiedBackground: #1b81a8;
--vscode-editorGutter-addedBackground: #487e02;
--vscode-editorGutter-deletedBackground: #f14c4c;
--vscode-minimapGutter-modifiedBackground: #1b81a8;
--vscode-minimapGutter-addedBackground: #487e02;
--vscode-minimapGutter-deletedBackground: #f14c4c;
--vscode-editorOverviewRuler-modifiedForeground: rgba(27, 129, 168, 0.6);
--vscode-editorOverviewRuler-addedForeground: rgba(72, 126, 2, 0.6);
--vscode-editorOverviewRuler-deletedForeground: rgba(241, 76, 76, 0.6);
--vscode-debugExceptionWidget-border: #a31515;
--vscode-debugExceptionWidget-background: #420b0d;
--vscode-editor-inlineValuesForeground: rgba(255, 255, 255, 0.5);
--vscode-editor-inlineValuesBackground: rgba(255, 200, 0, 0.2);
--vscode-statusBar-debuggingBackground: #cc6633;
--vscode-statusBar-debuggingForeground: #ffffff;
--vscode-commandCenter-debuggingBackground: rgba(204, 102, 51, 0.26);
--vscode-debugTokenExpression-name: #c586c0;
--vscode-debugTokenExpression-type: #4a90e2;
--vscode-debugTokenExpression-value: rgba(204, 204, 204, 0.6);
--vscode-debugTokenExpression-string: #ce9178;
--vscode-debugTokenExpression-boolean: #4e94ce;
--vscode-debugTokenExpression-number: #b5cea8;
--vscode-debugTokenExpression-error: #f48771;
--vscode-debugView-exceptionLabelForeground: #cccccc;
--vscode-debugView-exceptionLabelBackground: #6c2022;
--vscode-debugView-stateLabelForeground: #cccccc;
--vscode-debugView-stateLabelBackground: rgba(136, 136, 136, 0.27);
--vscode-debugView-valueChangedHighlight: #569cd6;
--vscode-debugConsole-infoForeground: #3794ff;
--vscode-debugConsole-warningForeground: #cca700;
--vscode-debugConsole-errorForeground: #f48771;
--vscode-debugConsole-sourceForeground: #cccccc;
--vscode-debugConsoleInputIcon-foreground: #cccccc;
--vscode-debugIcon-pauseForeground: #75beff;
--vscode-debugIcon-stopForeground: #f48771;
--vscode-debugIcon-disconnectForeground: #f48771;
--vscode-debugIcon-restartForeground: #89d185;
--vscode-debugIcon-stepOverForeground: #75beff;
--vscode-debugIcon-stepIntoForeground: #75beff;
--vscode-debugIcon-stepOutForeground: #75beff;
--vscode-debugIcon-continueForeground: #75beff;
--vscode-debugIcon-stepBackForeground: #75beff;
--vscode-mergeEditor-change-background: rgba(155, 185, 85, 0.2);
--vscode-mergeEditor-change-word-background: rgba(156, 204, 44, 0.2);
--vscode-mergeEditor-changeBase-background: #4b1818;
--vscode-mergeEditor-changeBase-word-background: #6f1313;
--vscode-mergeEditor-conflict-unhandledUnfocused-border: rgba(255, 166, 0, 0.48);
--vscode-mergeEditor-conflict-unhandledFocused-border: #ffa600;
--vscode-mergeEditor-conflict-handledUnfocused-border: rgba(134, 134, 134, 0.29);
--vscode-mergeEditor-conflict-handledFocused-border: rgba(193, 193, 193, 0.8);
--vscode-mergeEditor-conflict-handled-minimapOverViewRuler: rgba(173, 172, 168, 0.93);
--vscode-mergeEditor-conflict-unhandled-minimapOverViewRuler: #fcba03;
--vscode-mergeEditor-conflictingLines-background: rgba(255, 234, 0, 0.28);
--vscode-mergeEditor-conflict-input1-background: rgba(64, 200, 174, 0.2);
--vscode-mergeEditor-conflict-input2-background: rgba(64, 166, 255, 0.2);
--vscode-extensionIcon-starForeground: #ff8e00;
--vscode-extensionIcon-verifiedForeground: #3794ff;
--vscode-extensionIcon-preReleaseForeground: #1d9271;
--vscode-extensionIcon-sponsorForeground: #d758b3;
--vscode-terminal-ansiBlack: #000000;
--vscode-terminal-ansiRed: #cd3131;
--vscode-terminal-ansiGreen: #0dbc79;
--vscode-terminal-ansiYellow: #e5e510;
--vscode-terminal-ansiBlue: #2472c8;
--vscode-terminal-ansiMagenta: #bc3fbc;
--vscode-terminal-ansiCyan: #11a8cd;
--vscode-terminal-ansiWhite: #e5e5e5;
--vscode-terminal-ansiBrightBlack: #666666;
--vscode-terminal-ansiBrightRed: #f14c4c;
--vscode-terminal-ansiBrightGreen: #23d18b;
--vscode-terminal-ansiBrightYellow: #f5f543;
--vscode-terminal-ansiBrightBlue: #3b8eea;
--vscode-terminal-ansiBrightMagenta: #d670d6;
--vscode-terminal-ansiBrightCyan: #29b8db;
--vscode-terminal-ansiBrightWhite: #e5e5e5;
--vscode-simpleFindWidget-sashBorder: #454545;
--vscode-terminalCommandGuide-foreground: #37373d;
--vscode-welcomePage-tileBackground: #252526;
--vscode-welcomePage-tileHoverBackground: #2c2c2d;
--vscode-welcomePage-tileBorder: rgba(255, 255, 255, 0.1);
--vscode-welcomePage-progress-background: #3c3c3c;
--vscode-welcomePage-progress-foreground: #3794ff;
--vscode-walkthrough-stepTitle-foreground: #ffffff;
--vscode-walkThrough-embeddedEditorBackground: rgba(0, 0, 0, 0.4);
--vscode-profiles-sashBorder: rgba(128, 128, 128, 0.35);
--vscode-gitDecoration-addedResourceForeground: #81b88b;
--vscode-gitDecoration-modifiedResourceForeground: #e2c08d;
--vscode-gitDecoration-deletedResourceForeground: #c74e39;
--vscode-gitDecoration-renamedResourceForeground: #73c991;
--vscode-gitDecoration-untrackedResourceForeground: #73c991;
--vscode-gitDecoration-ignoredResourceForeground: #8c8c8c;
--vscode-gitDecoration-stageModifiedResourceForeground: #e2c08d;
--vscode-gitDecoration-stageDeletedResourceForeground: #c74e39;
--vscode-gitDecoration-conflictingResourceForeground: #e4676b;
--vscode-gitDecoration-submoduleResourceForeground: #8db9e2;
--vscode-gitlens-gutterBackgroundColor: rgba(255, 255, 255, 0.07);
--vscode-gitlens-gutterForegroundColor: #bebebe;
--vscode-gitlens-gutterUncommittedForegroundColor: rgba(0, 188, 242, 0.6);
--vscode-gitlens-trailingLineBackgroundColor: rgba(0, 0, 0, 0);
--vscode-gitlens-trailingLineForegroundColor: rgba(153, 153, 153, 0.35);
--vscode-gitlens-lineHighlightBackgroundColor: rgba(0, 188, 242, 0.2);
--vscode-gitlens-lineHighlightOverviewRulerColor: rgba(0, 188, 242, 0.6);
--vscode-gitlens-openAutolinkedIssueIconColor: #3fb950;
--vscode-gitlens-closedAutolinkedIssueIconColor: #a371f7;
--vscode-gitlens-closedPullRequestIconColor: #f85149;
--vscode-gitlens-openPullRequestIconColor: #3fb950;
--vscode-gitlens-mergedPullRequestIconColor: #a371f7;
--vscode-gitlens-unpublishedChangesIconColor: #35b15e;
--vscode-gitlens-unpublishedCommitIconColor: #35b15e;
--vscode-gitlens-unpulledChangesIconColor: #b15e35;
--vscode-gitlens-decorations-addedForegroundColor: #81b88b;
--vscode-gitlens-decorations-copiedForegroundColor: #73c991;
--vscode-gitlens-decorations-deletedForegroundColor: #c74e39;
--vscode-gitlens-decorations-ignoredForegroundColor: #8c8c8c;
--vscode-gitlens-decorations-modifiedForegroundColor: #e2c08d;
--vscode-gitlens-decorations-untrackedForegroundColor: #73c991;
--vscode-gitlens-decorations-renamedForegroundColor: #73c991;
--vscode-gitlens-decorations-branchAheadForegroundColor: #35b15e;
--vscode-gitlens-decorations-branchBehindForegroundColor: #b15e35;
--vscode-gitlens-decorations-branchDivergedForegroundColor: #d8af1b;
--vscode-gitlens-decorations-branchMissingUpstreamForegroundColor: #c74e39;
--vscode-gitlens-decorations-statusMergingOrRebasingConflictForegroundColor: #c74e39;
--vscode-gitlens-decorations-statusMergingOrRebasingForegroundColor: #d8af1b;
--vscode-gitlens-decorations-workspaceRepoMissingForegroundColor: #909090;
--vscode-gitlens-decorations-workspaceCurrentForegroundColor: #35b15e;
--vscode-gitlens-decorations-workspaceRepoOpenForegroundColor: #35b15e;
--vscode-gitlens-decorations-worktreeHasUncommittedChangesForegroundColor: #e2c08d;
--vscode-gitlens-decorations-worktreeMissingForegroundColor: #c74e39;
--vscode-gitlens-graphLane1Color: #15a0bf;
--vscode-gitlens-graphLane2Color: #0669f7;
--vscode-gitlens-graphLane3Color: #8e00c2;
--vscode-gitlens-graphLane4Color: #c517b6;
--vscode-gitlens-graphLane5Color: #d90171;
--vscode-gitlens-graphLane6Color: #cd0101;
--vscode-gitlens-graphLane7Color: #f25d2e;
--vscode-gitlens-graphLane8Color: #f2ca33;
--vscode-gitlens-graphLane9Color: #7bd938;
--vscode-gitlens-graphLane10Color: #2ece9d;
--vscode-gitlens-graphChangesColumnAddedColor: #347d39;
--vscode-gitlens-graphChangesColumnDeletedColor: #c93c37;
--vscode-gitlens-graphMinimapMarkerHeadColor: #05e617;
--vscode-gitlens-graphScrollMarkerHeadColor: #05e617;
--vscode-gitlens-graphMinimapMarkerUpstreamColor: #09ae17;
--vscode-gitlens-graphScrollMarkerUpstreamColor: #09ae17;
--vscode-gitlens-graphMinimapMarkerHighlightsColor: #fbff0a;
--vscode-gitlens-graphScrollMarkerHighlightsColor: #fbff0a;
--vscode-gitlens-graphMinimapMarkerLocalBranchesColor: #3087cf;
--vscode-gitlens-graphScrollMarkerLocalBranchesColor: #3087cf;
--vscode-gitlens-graphMinimapMarkerPullRequestsColor: #c76801;
--vscode-gitlens-graphScrollMarkerPullRequestsColor: #c76801;
--vscode-gitlens-graphMinimapMarkerRemoteBranchesColor: #2b5e88;
--vscode-gitlens-graphScrollMarkerRemoteBranchesColor: #2b5e88;
--vscode-gitlens-graphMinimapMarkerStashesColor: #b34db3;
--vscode-gitlens-graphScrollMarkerStashesColor: #b34db3;
--vscode-gitlens-graphMinimapMarkerTagsColor: #6b562e;
--vscode-gitlens-graphScrollMarkerTagsColor: #6b562e;
--vscode-gitlens-launchpadIndicatorMergeableColor: #3fb950;
--vscode-gitlens-launchpadIndicatorMergeableHoverColor: #3fb950;
--vscode-gitlens-launchpadIndicatorBlockedColor: #c74e39;
--vscode-gitlens-launchpadIndicatorBlockedHoverColor: #c74e39;
--vscode-gitlens-launchpadIndicatorAttentionColor: #d8af1b;
--vscode-gitlens-launchpadIndicatorAttentionHoverColor: #d8af1b;
--vscode-remoteHub-decorations-addedForegroundColor: #81b88b;
--vscode-remoteHub-decorations-modifiedForegroundColor: #e2c08d;
--vscode-remoteHub-decorations-deletedForegroundColor: #c74e39;
--vscode-remoteHub-decorations-submoduleForegroundColor: #8db9e2;
--vscode-remoteHub-decorations-conflictForegroundColor: #e4676b;
--vscode-remoteHub-decorations-incomingAddedForegroundColor: #81b88b;
--vscode-remoteHub-decorations-incomingModifiedForegroundColor: #e2c08d;
--vscode-remoteHub-decorations-incomingDeletedForegroundColor: #c74e39;
--vscode-remoteHub-decorations-incomingRenamedForegroundColor: #73c991;
--vscode-remoteHub-decorations-possibleConflictForegroundColor: #cca700;
--vscode-remoteHub-decorations-ignoredResourceForeground: #8c8c8c;
--vscode-remoteHub-decorations-workspaceRepositoriesView-hasUncommittedChangesForegroundColor: #e2c08d;
--vscode-rust_analyzer-syntaxTreeBorder: #ffffff;
}

71
news/public/favicon.svg Normal file
View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="612" height="612" viewBox="0 0 612 612" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="gradient_1" gradientUnits="userSpaceOnUse" x1="300" y1="0" x2="300" y2="600">
<stop offset="0" stop-color="#A1A7F6" />
<stop offset="1" stop-color="#FFFFFF" stop-opacity="0.2" />
</linearGradient>
<linearGradient id="gradient_2" gradientUnits="userSpaceOnUse" x1="110.5" y1="0" x2="110.5" y2="221">
<stop offset="0.468" stop-color="#BFBAF6" />
<stop offset="1" stop-color="#FFFFFF" />
</linearGradient>
<filter color-interpolation-filters="sRGB" x="-219" y="-219" width="221" height="221" id="filter_3">
<feFlood flood-opacity="0" result="BackgroundImageFix_1" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" in="SourceAlpha" />
<feOffset dx="0" dy="4" />
<feGaussianBlur stdDeviation="2" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.251 0" />
<feBlend mode="normal" in2="BackgroundImageFix_1" result="Shadow_2" />
<feBlend mode="normal" in="SourceGraphic" in2="Shadow_2" result="Shape_3" />
</filter>
<linearGradient id="gradient_4" gradientUnits="userSpaceOnUse" x1="55.5" y1="0" x2="55.5" y2="111">
<stop offset="0" stop-color="#FFFFFF" />
<stop offset="1" stop-color="#A8A7F3" />
</linearGradient>
<filter color-interpolation-filters="sRGB" x="-109" y="-109" width="111" height="111" id="filter_5">
<feFlood flood-opacity="0" result="BackgroundImageFix_1" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" in="SourceAlpha" />
<feOffset dx="0" dy="4" />
<feGaussianBlur stdDeviation="2" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.251 0" />
<feBlend mode="normal" in2="BackgroundImageFix_1" result="Shadow_2" />
<feBlend mode="normal" in="SourceGraphic" in2="Shadow_2" result="Shape_3" />
</filter>
<linearGradient id="gradient_6" gradientUnits="userSpaceOnUse" x1="174" y1="0" x2="174" y2="348">
<stop offset="0.182" stop-color="#A594F6" />
<stop offset="1" stop-color="#F4E5FF" />
</linearGradient>
<filter color-interpolation-filters="sRGB" x="-346" y="-346" width="348" height="348" id="filter_7">
<feFlood flood-opacity="0" result="BackgroundImageFix_1" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" in="SourceAlpha" />
<feOffset dx="0" dy="4" />
<feGaussianBlur stdDeviation="2" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.251 0" />
<feBlend mode="normal" in2="BackgroundImageFix_1" result="Shadow_2" />
<feBlend mode="normal" in="SourceGraphic" in2="Shadow_2" result="Shape_3" />
</filter>
<linearGradient id="gradient_8" gradientUnits="userSpaceOnUse" x1="57" y1="0" x2="57" y2="114">
<stop offset="0" stop-color="#FFFFFF" />
<stop offset="0.614" stop-color="#C7BAF8" />
</linearGradient>
<filter color-interpolation-filters="sRGB" x="-112" y="-112" width="114" height="114" id="filter_9">
<feFlood flood-opacity="0" result="BackgroundImageFix_1" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" in="SourceAlpha" />
<feOffset dx="0" dy="4" />
<feGaussianBlur stdDeviation="2" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.251 0" />
<feBlend mode="normal" in2="BackgroundImageFix_1" result="Shadow_2" />
<feBlend mode="normal" in="SourceGraphic" in2="Shadow_2" result="Shape_3" />
</filter>
</defs>
<g transform="translate(6 2)">
<g>
<path d="M300 0C465.708 0 600 134.292 600 300C600 300 600 300 600 300C600 465.708 465.708 600 300 600C300 600 300 600 300 600C134.292 600 0 465.708 0 300C0 300 0 300 0 300C0 134.292 134.292 0 300 0Z" fill="#5A00FF" fill-rule="evenodd" />
<path d="M300 0C465.708 0 600 134.292 600 300C600 300 600 300 600 300C600 465.708 465.708 600 300 600C300 600 300 600 300 600C134.292 600 0 465.708 0 300C0 300 0 300 0 300C0 134.292 134.292 0 300 0Z" fill="url(#gradient_1)" fill-rule="evenodd" />
</g>
<path d="M0 110.5C0 49.4725 49.4725 0 110.5 0C171.527 0 221 49.4725 221 110.5C221 171.527 171.527 221 110.5 221C49.4725 221 0 171.527 0 110.5Z" fill="url(#gradient_2)" fill-rule="evenodd" filter="url(#filter_3)" transform="translate(293 324)" />
<path d="M0 55.5C0 24.8482 24.8482 0 55.5 0C86.1518 0 111 24.8482 111 55.5C111 86.1518 86.1518 111 55.5 111C24.8482 111 0 86.1518 0 55.5Z" fill="url(#gradient_4)" fill-rule="evenodd" filter="url(#filter_5)" transform="translate(48 269)" />
<path d="M0 174C0 77.9024 77.9024 0 174 0C270.098 0 348 77.9024 348 174C348 270.098 270.098 348 174 348C77.9024 348 0 270.098 0 174Z" fill="url(#gradient_6)" fill-rule="evenodd" filter="url(#filter_7)" transform="translate(188 56)" />
<path d="M0 57C0 25.5198 25.5198 0 57 0C88.4802 0 114 25.5198 114 57C114 88.4802 88.4802 114 57 114C25.5198 114 0 88.4802 0 57Z" fill="url(#gradient_8)" fill-rule="evenodd" filter="url(#filter_9)" transform="translate(388 129)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

115
news/src/App.vue Normal file
View File

@ -0,0 +1,115 @@
<script setup lang="ts">
import OmHeader from './components/Header.vue';
import OmWhatNews from './components/WhatNews.vue';
import OmMoreFeature from './components/MoreFeature.vue';
import OmCoreFeature from './components/CoreFeature.vue';
import OmSponsor from './components/Sponsor.vue';
import OmResource from './components/Resource.vue';
import OmTroubleshoot from './components/Troubleshoot.vue';
import OmContributor from './components/Contributor.vue';
import data from './data.json';``
</script>
<template>
<main class="openmcp-news-root">
<om-header :version="data.version" />
<!-- 1. 📣 What is news in OpenMCP -->
<om-what-news :version="data.version" :changelogs="data.changelogs" />
<!-- 2. 🐳 Learn more features -->
<om-more-feature />
<!-- 3. Core Features -->
<om-core-feature />
<!-- 4. How to sponsor -->
<om-sponsor />
<!-- 5. 📚 Resources -->
<om-resource />
<!-- 6. 🔧 Troubleshooting -->
<om-troubleshoot />
<!-- 7. 👥 Contributors -->
<om-contributor :contributors="data.contributors" />
</main>
</template>
<style>
body {
color: var(--foreground, #222);
background: var(--background, #fff);
font-size: var(--vscode-font-size);
max-width: 900px;
margin: 0 auto;
padding: 2rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
line-height: 1.6;
}
.openmcp-news-root {
color: var(--foreground, #222);
background: var(--background, #fff);
padding: 32px 0 64px 0;
min-height: 100vh;
box-sizing: border-box;
max-width: 820px;
margin: 0 auto;
}
.release-link {
color: #B988D1;
font-weight: 500;
text-decoration: underline;
margin-bottom: 8px;
display: inline-block;
transition: color 0.2s;
}
.release-link:hover {
color: #8e5bbf;
}
.news-list {
list-style: none;
padding: 0;
margin: 0;
}
.news-badge {
display: inline-block;
background: #B988D1;
color: #fff;
border-radius: 8px;
font-size: 0.85em;
padding: 2px 10px;
margin-right: 10px;
font-weight: 500;
vertical-align: middle;
}
.openmcp-header {
display: flex;
justify-content: center;
align-items: center;
gap: 24px;
margin: 0 0 32px 0;
padding: 0 0 16px 0;
}
@media screen and (max-width: 600px) {
.openmcp-header {
flex-direction: column;
align-items: center;
}
}
.margin-bottom {
margin-bottom: 32px;
}
</style>

View File

@ -0,0 +1,98 @@
<script setup lang="ts">
import { ref, onMounted, reactive } from 'vue';
import VanillaTilt from 'vanilla-tilt';
const props = defineProps<{
contributors: {
username: string;
avatarUrl: string;
homeUrl: string;
}[];
}>();
const tiltElements = reactive<any[]>([]);
onMounted(() => {
// Tilt
tiltElements.forEach(el => {
VanillaTilt.init(el, {
max: 35,
speed: 400,
glare: true,
'max-glare': 0.2
});
});
});
</script>
<template>
<section class="troubleshoot-section">
<div class="section-title">👥 Contributors</div>
<div class="contributors-grid">
<div v-for="(contributor, idx) in props.contributors"
:key="contributor.username" class="contributor-tilt"
:ref="el => tiltElements[idx] = el"
>
<a :href="contributor.homeUrl" target="_blank">
<img :src="contributor.avatarUrl" :alt="contributor.username" class="contributor-avatar" />
<span class="contributor-name">{{ contributor.username }}</span>
</a>
</div>
</div>
</section>
</template>
<style>
.contributors-container {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.contributors-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.contributor-tilt {
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 1rem;
transition: transform 0.2s;
}
.contributor-tilt a {
text-decoration: none;
color: inherit;
display: flex;
flex-direction: column;
align-items: center;
}
.contributor-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
display: block;
margin: 0 auto 0.5rem;
border: 3px solid #fff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.contributor-name {
display: block;
font-weight: 500;
color: #B988D1;
text-decoration: none;
}
/* Tilt.js 生成的眩光效果自定义 */
.tilt-glare {
border-radius: 12px !important;
}
</style>

View File

@ -0,0 +1,92 @@
<template>
<section class="core-section">
<div class="section-title"> Core Features</div>
<ul class="core-list">
<li class="core-card">
<span class="core-dot">🧩</span>
<span>Full support for all MCP protocols, including <b>stdio</b>, <b>SSE</b>, and <b>streamable HTTP</b> connection modes.</span>
<a href="https://openmcp.kirigaya.cn/plugin-tutorial/usage/debug.html" target="_blank">Learn more</a>
</li>
<li class="core-card">
<span class="core-dot">🧩</span>
<span>Connect Multiple MCP Servers and combine more mcp tools in just one instance.</span>
<a href="https://openmcp.kirigaya.cn/plugin-tutorial/usage/multi-server.html" target="_blank">Learn more</a>
</li>
<li class="core-card">
<span class="core-dot">🧩</span>
<span>One-click AI Mock data generation & Automatic mcp tool testing width custom and flexible order.</span>
<a href="https://openmcp.kirigaya.cn/sdk-tutorial/" target="_blank">Learn more</a>
</li>
<li class="core-card">
<span class="core-dot">🧩</span>
<span>Support function calling based on both <strong>XML</strong> and <strong>openai tools protocols.</strong></span>
<a href="https://openmcp.kirigaya.cn/sdk-tutorial/" target="_blank">Learn more</a>
</li>
<li class="core-card">
<span class="core-dot">🧩</span>
<span>Export connection as mcpconfig.json and deploy in openmcp-sdk</span>
<a href="https://openmcp.kirigaya.cn/sdk-tutorial/" target="_blank">Learn more</a>
</li>
<li class="core-card">
<span class="core-dot">🧩</span>
<span>Native VSCode experience, cross-platform support</span>
<a href="https://openmcp.kirigaya.cn/plugin-tutorial/quick-start/acquire-openmcp.html" target="_blank">Learn more</a>
</li>
</ul>
</section>
</template>
<style scoped>
.core-section {
margin-bottom: 20px;
}
.core-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 18px;
list-style: none;
padding: 0;
margin: 0;
}
@media (max-width: 950px) {
.core-list {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 630px) {
.core-list {
grid-template-columns: 1fr;
}
}
.core-card {
display: flex;
align-items: flex-start;
flex-direction: column;
gap: 12px;
padding: 1.5rem;
border-radius: 10px;
background-color: var(--vscode-sideBar-background);
transition: transform 0.2s ease, box-shadow 0.2s;
box-shadow: 0 2px 8px var(--vscode-widget-shadow, rgba(0,0,0,0.08));
font-size: 1.05em;
}
.core-card a {
color: #B988D1;
}
.core-card:hover {
transform: translateY(-4px) scale(1.025);
box-shadow: 0 6px 18px var(--vscode-widget-shadow, rgba(0,0,0,0.14));
}
.core-dot {
display: inline-block;
border-radius: 50%;
font-size: 30px;
flex-shrink: 0;
}
</style>

View File

@ -0,0 +1,119 @@
<script setup lang="ts">
const props = defineProps({
version: {
type: String,
default: '0.1.9'
}
});
</script>
<template>
<header class="openmcp-header">
<div class="openmcp-icon openmcp-logo"></div>
<div>
<h1>
OpenMCP Client<sup><small>{{ props.version }}</small></sup>
</h1>
<div class="links">
<a href="https://github.com/LSTM-Kirigaya/openmcp-client" target="_blank" title="GitHub">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"
style="vertical-align: middle;">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z">
</path>
</svg>
GitHub
</a>
<a href="https://github.com/LSTM-Kirigaya/openmcp-client/releases" target="_blank" title="Releases">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"
style="vertical-align: middle;">
<path
d="M21 10.975V8a2 2 0 00-2-2h-6V4.688c.305-.274.5-.668.5-1.11a1.5 1.5 0 00-3 0c0 .442.195.836.5 1.11V6H5a2 2 0 00-2 2v2.998l-.072.005A.999.999 0 002 12v2a1 1 0 001 1v5a2 2 0 002 2h14a2 2 0 002-2v-5a1 1 0 001-1v-1.975a1 1 0 00-.928-.997L21 10.975zm-5 5.025H8v-3h8v3z">
</path>
</svg>
Releases
</a>
<a href="https://openmcp.kirigaya.cn/" target="_blank" title="Website">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"
style="vertical-align: middle;">
<path
d="M12.001 1.993C6.486 1.994 2 6.48 2 11.994c0 5.514 4.486 9.999 10 10 5.515 0 10.001-4.485 10.001-10s-4.486-10-10-10.001zM12 19.994c-4.412 0-8.001-3.589-8.001-8s3.589-8 8-8.001C16.411 3.994 20 7.583 20 11.994c0 4.41-3.589 8-8 8z">
</path>
<path
d="M12.001 8.994l-4.005 4.005 1.414 1.414 2.591-2.591 2.591 2.591 1.414-1.414-4.005-4.005z">
</path>
</svg>
Website
</a>
<a href="https://discord.com/invite/SKTZRf6NzU" target="_blank" title="Discord">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"
style="vertical-align: middle;">
<path
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 00-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 00-4.8 0c-.14-.34-.35-.76-.54-1.09-.01-.02-.04-.03-.07-.03-1.5.26-2.93.71-4.27 1.33-.01 0-.02.01-.03.02-2.72 4.07-3.47 8.03-3.1 11.95 0 .02.01.04.03.05 1.8 1.32 3.53 2.12 5.24 2.65.03.01.06 0 .07-.02.4-.55.76-1.13 1.07-1.74.02-.04 0-.08-.04-.09-.57-.22-1.11-.48-1.64-.78-.04-.02-.04-.08-.01-.11.11-.08.22-.17.33-.25.02-.02.05-.02.07-.01 3.44 1.57 7.15 1.57 10.55 0 .02-.01.05-.01.07.01.11.09.22.17.33.26.04.03.04.09-.01.11-.52.31-1.07.56-1.64.78-.04.01-.05.06-.04.09.32.61.68 1.19 1.07 1.74.03.01.06.02.09.01 1.72-.53 3.45-1.33 5.25-2.65.02-.01.03-.03.03-.05.44-4.53-.73-8.46-3.1-11.95-.01-.01-.02-.02-.04-.02zM8.52 14.91c-.99 0-1.8-.9-1.8-2s.79-2.01 1.8-2.01c1 0 1.81.9 1.81 2.01-.01 1.1-.8 2-1.81 2zm6.96 0c-.99 0-1.8-.9-1.8-2s.79-2.01 1.8-2.01c1 0 1.81.9 1.81 2.01-.01 1.1-.8 2-1.81 2z">
</path>
</svg>
Discord
</a>
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD"
target="_blank" title="QQ Group">
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor">
<path d="M824.8 613.2c-16-51.4-34.4-94.6-62.7-165.3C766.5 262.2 689.3 112 511.5 112 331.7 112 256.2 265.2 261 447.9c-28.4 70.8-46.7 113.7-62.7 165.3-34 109.5-23 154.8-14.6 155.8 18 2.2 70.1-82.4 70.1-82.4 0 49 25.2 112.9 79.8 159-26.4 8.1-85.7 29.9-71.6 53.8 11.4 19.3 196.2 12.3 249.5 6.3 53.3 6 238.1 13 249.5-6.3 14.1-23.8-45.3-45.7-71.6-53.8 54.6-46.2 79.8-110.1 79.8-159 0 0 52.1 84.6 70.1 82.4 8.5-1.1 19.5-46.4-14.5-155.8z"></path>
</svg>
QQ Group
</a>
</div>
</div>
</header>
<hr>
</template>
<style>
.openmcp-logo {
display: inline-block;
width: 84px;
height: 84px;
border-radius: 16px;
vertical-align: middle;
}
hr {
border: none;
border-top: 1.5px solid #B988D1;
height: 0;
margin: 24px 0;
}
.openmcp-header h1 {
font-size: 2.1rem;
font-weight: bold;
margin: 0 0 20px 0;
display: flex;
justify-content: center;
color: #B988D1;
line-height: 1.2;
}
.openmcp-header .links {
display: flex;
flex-wrap: wrap;
gap: 16px;
justify-content: center;
margin-top: 4px;
}
.openmcp-header .links a {
color: var(--vscode-textLink-foreground);
font-weight: 500;
text-decoration: none;
display: flex;
align-items: center;
gap: 0.5rem;
transition: color 0.18s;
}
.openmcp-header .links a:hover {
color: #8e5bbf;
text-decoration: underline;
}
</style>

View File

@ -0,0 +1,5 @@
<template>
<div>
</div>
</template>

View File

@ -0,0 +1,118 @@
<script setup lang="ts">
import VanillaTilt from 'vanilla-tilt';
import { onMounted, ref } from 'vue';
const container = ref<any>(null);
onMounted(() => {
VanillaTilt.init(container.value, {
max: 12,
speed: 400,
glare: true,
'max-glare': 0.5
});
});
</script>
<template>
<section class="feature-section">
<div class="section-title">🐳 Learn More Features & Usage</div>
<div style="margin-bottom: 1.1rem;">Learn more about how to use openmcp to build your AI Agent & MCP fast</div>
<!-- <a href="https://www.youtube.com/embed/S7igsEhcLiw?si=6sqvbYJxSRoFS26g" target="_blank" class="bilibili-player-container" style="display:flex; width: 100%; justify-content: center;">
<iframe width="100%" height="580" src="https://www.youtube.com/embed/S7igsEhcLiw?si=6sqvbYJxSRoFS26g" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</a> -->
<a href="https://www.youtube.com/watch?v=S7igsEhcLiw" target="_blank" class="bilibili-player-container">
<img src="https://pic1.zhimg.com/80/v2-951261f789708621a2c34faa5fa6f330_1440w.png" alt="" ref="container">
</a>
</section>
</template>
<style>
.feature-section {
margin-bottom: 20px;
}
.section-title {
font-size: 1.5rem;
font-weight: bold;
color: #B988D1;
margin-bottom: 12px;
}
.feature-cards {
display: flex;
flex-wrap: wrap;
gap: 18px;
}
.feature-card {
background: none;
border: 1.5px solid #B988D1;
border-radius: 10px;
padding: 18px 22px;
min-width: 180px;
flex: 1 1 180px;
color: var(--foreground, #222);
text-decoration: none;
transition: box-shadow 0.15s, border 0.15s;
box-shadow: 0 2px 8px 0 rgba(185, 136, 209, 0.04);
display: flex;
flex-direction: column;
gap: 6px;
}
.feature-card:hover {
border-color: #8e5bbf;
box-shadow: 0 4px 16px 0 rgba(185, 136, 209, 0.12);
}
.feature-card-title {
font-weight: bold;
color: #B988D1;
font-size: 1.1em;
}
.feature-card-desc {
font-size: 0.98em;
color: var(--foreground, #444);
opacity: 0.85;
}
.bilibili-player-container img {
max-width: 100%;
box-shadow: 0 4px 12px var(--vscode-widget-shadow);
border-radius: .5em;
}
.bilibili-player-container {
position: relative;
width: fit-content;
height: fit-content;
margin: 2rem 0;
border-radius: .5em;
overflow: hidden;
transition: box-shadow 0.3s ease, border-color 0.3s ease;
}
@media (max-width: 2700px) {
.bilibili-player-container {
width: 88%;
}
}
@media (max-width: 1200px) {
.bilibili-player-container {
width: 95%;
}
}
@media screen and (max-width: 741px) {
.bilibili-player-container iframe {
width: 100%;
}
}
</style>

View File

@ -0,0 +1,98 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
const svgString = `
<svg width="84" height="84" viewBox="0 0 612 612" fill="none" xmlns="http://www.w3.org/2000/svg" class="openmcp-logo">
<defs>
<linearGradient id="gradient_1" gradientUnits="userSpaceOnUse" x1="300" y1="0" x2="300" y2="600">
<stop offset="0" stop-color="#A1A7F6" />
<stop offset="1" stop-color="#FFFFFF" stop-opacity="0.2" />
</linearGradient>
<linearGradient id="gradient_2" gradientUnits="userSpaceOnUse" x1="110.5" y1="0" x2="110.5" y2="221">
<stop offset="0.468" stop-color="#BFBAF6" />
<stop offset="1" stop-color="#FFFFFF" />
</linearGradient>
<filter color-interpolation-filters="sRGB" x="-219" y="-219" width="221" height="221" id="filter_3">
<feFlood flood-opacity="0" result="BackgroundImageFix_1" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" in="SourceAlpha" />
<feOffset dx="0" dy="4" />
<feGaussianBlur stdDeviation="2" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.251 0" />
<feBlend mode="normal" in2="BackgroundImageFix_1" result="Shadow_2" />
<feBlend mode="normal" in="SourceGraphic" in2="Shadow_2" result="Shape_3" />
</filter>
<linearGradient id="gradient_4" gradientUnits="userSpaceOnUse" x1="55.5" y1="0" x2="55.5" y2="111">
<stop offset="0" stop-color="#FFFFFF" />
<stop offset="1" stop-color="#A8A7F3" />
</linearGradient>
<filter color-interpolation-filters="sRGB" x="-109" y="-109" width="111" height="111" id="filter_5">
<feFlood flood-opacity="0" result="BackgroundImageFix_1" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" in="SourceAlpha" />
<feOffset dx="0" dy="4" />
<feGaussianBlur stdDeviation="2" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.251 0" />
<feBlend mode="normal" in2="BackgroundImageFix_1" result="Shadow_2" />
<feBlend mode="normal" in="SourceGraphic" in2="Shadow_2" result="Shape_3" />
</filter>
<linearGradient id="gradient_6" gradientUnits="userSpaceOnUse" x1="174" y1="0" x2="174" y2="348">
<stop offset="0.182" stop-color="#A594F6" />
<stop offset="1" stop-color="#F4E5FF" />
</linearGradient>
<filter color-interpolation-filters="sRGB" x="-346" y="-346" width="348" height="348" id="filter_7">
<feFlood flood-opacity="0" result="BackgroundImageFix_1" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" in="SourceAlpha" />
<feOffset dx="0" dy="4" />
<feGaussianBlur stdDeviation="2" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.251 0" />
<feBlend mode="normal" in2="BackgroundImageFix_1" result="Shadow_2" />
<feBlend mode="normal" in="SourceGraphic" in2="Shadow_2" result="Shape_3" />
</filter>
<linearGradient id="gradient_8" gradientUnits="userSpaceOnUse" x1="57" y1="0" x2="57" y2="114">
<stop offset="0" stop-color="#FFFFFF" />
<stop offset="0.614" stop-color="#C7BAF8" />
</linearGradient>
<filter color-interpolation-filters="sRGB" x="-112" y="-112" width="114" height="114" id="filter_9">
<feFlood flood-opacity="0" result="BackgroundImageFix_1" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" in="SourceAlpha" />
<feOffset dx="0" dy="4" />
<feGaussianBlur stdDeviation="2" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.251 0" />
<feBlend mode="normal" in2="BackgroundImageFix_1" result="Shadow_2" />
<feBlend mode="normal" in="SourceGraphic" in2="Shadow_2" result="Shape_3" />
</filter>
</defs>
<g transform="translate(6 2)">
<g>
<path d="M300 0C465.708 0 600 134.292 600 300C600 300 600 300 600 300C600 465.708 465.708 600 300 600C300 600 300 600 300 600C134.292 600 0 465.708 0 300C0 300 0 300 0 300C0 134.292 134.292 0 300 0Z" fill="#5A00FF" fill-rule="evenodd" />
<path d="M300 0C465.708 0 600 134.292 600 300C600 300 600 300 600 300C600 465.708 465.708 600 300 600C300 600 300 600 300 600C134.292 600 0 465.708 0 300C0 300 0 300 0 300C0 134.292 134.292 0 300 0Z" fill="url(#gradient_1)" fill-rule="evenodd" />
</g>
<path d="M0 110.5C0 49.4725 49.4725 0 110.5 0C171.527 0 221 49.4725 221 110.5C221 171.527 171.527 221 110.5 221C49.4725 221 0 171.527 0 110.5Z" fill="url(#gradient_2)" fill-rule="evenodd" filter="url(#filter_3)" transform="translate(293 324)" />
<path d="M0 55.5C0 24.8482 24.8482 0 55.5 0C86.1518 0 111 24.8482 111 55.5C111 86.1518 86.1518 111 55.5 111C24.8482 111 0 86.1518 0 55.5Z" fill="url(#gradient_4)" fill-rule="evenodd" filter="url(#filter_5)" transform="translate(48 269)" />
<path d="M0 174C0 77.9024 77.9024 0 174 0C270.098 0 348 77.9024 348 174C348 270.098 270.098 348 174 348C77.9024 348 0 270.098 0 174Z" fill="url(#gradient_6)" fill-rule="evenodd" filter="url(#filter_7)" transform="translate(188 56)" />
<path d="M0 57C0 25.5198 25.5198 0 57 0C88.4802 0 114 25.5198 114 57C114 88.4802 88.4802 114 57 114C25.5198 114 0 88.4802 0 57Z" fill="url(#gradient_8)" fill-rule="evenodd" filter="url(#filter_9)" transform="translate(388 129)" />
</g>
</svg>
`;
const svgContainer = ref<HTMLElement | null>(null);
onMounted(() => {
if (svgContainer.value) {
svgContainer.value.innerHTML = svgString;
}
});
</script>
<template>
<span ref="svgContainer" class="openmcp-logo"></span>
</template>
<style>
.openmcp-logo {
display: inline-block;
width: 84px;
height: 84px;
border-radius: 16px;
vertical-align: middle;
}
</style>

View File

@ -0,0 +1,45 @@
<template>
<section class="resource-section">
<div class="section-title">📚 Resources</div>
<ul class="resource-list">
<li><a data-v-dd258f77="" class="VPNolebaseInlinePreviewLink" relative=""
href="https://www.bilibili.com/video/BV1zYGozgEHc" target="_blank" rel="noreferrer">MCP Series Video
Tutorials (Under Construction)</a></li>
<li><a data-v-dd258f77="" class="VPNolebaseInlinePreviewLink" relative=""
href="https://kirigaya.cn/blog/search?q=mcp" target="_blank" rel="noreferrer">Jin Hui's MCP Series
Blog</a></li>
<li><a data-v-dd258f77="" class="VPNolebaseInlinePreviewLink" relative=""
href="https://openmcp.kirigaya.cn/plugin-tutorial" target="_blank" rel="noreferrer">OpenMCP Official
Documentation</a></li>
<li><a data-v-dd258f77="" class="VPNolebaseInlinePreviewLink" relative=""
href="https://openmcp.kirigaya.cn/sdk-tutorial" target="_blank" rel="noreferrer">openmcp-sdk
Official Documentation</a></li>
</ul>
</section>
</template>
<style>
.resource-section {
margin-bottom: 20px;
padding-bottom: 16px;
}
.resource-list {
padding: 0;
margin: 0;
padding-left: 28px;
}
.resource-list li {
margin-bottom: 0.75rem;
}
.resource-list a {
color: #B988D1;
transition: color 0.2s;
}
.resource-list a:hover {
color: #8e5bbf;
}
</style>

View File

@ -0,0 +1,61 @@
<template>
<section class="sponsor-section">
<div class="section-title"> How to Sponsor</div>
<div class="sponsor-content">
<p>If you like openmcp, please support our work!</p>
<a class="sponsor-item" href="https://github.com/LSTM-Kirigaya/openmcp-client" target="_blank">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="vertical-align: middle;">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z">
</path>
</svg>
Github
</a>
<br>
<a class="sponsor-item" href="https://afdian.com/a/kirigaya" target="_blank">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm.31-8.86c-1.77-.45-2.34-.94-2.34-1.67 0-.84.79-1.43 2.1-1.43 1.38 0 1.9.66 1.94 1.64h1.71c-.05-1.34-.87-2.57-2.49-2.97V5H10.9v1.69c-1.51.32-2.72 1.3-2.72 2.81 0 1.79 1.49 2.69 3.66 3.21 1.95.46 2.34 1.15 2.34 1.87 0 .53-.39 1.39-2.1 1.39-1.6 0-2.23-.72-2.32-1.64H8.04c.1 1.7 1.36 2.66 2.86 2.97V19h2.34v-1.67c1.52-.29 2.72-1.16 2.73-2.77-.01-2.2-1.9-2.96-3.66-3.42z">
</path>
</svg>
爱发电</a>
</div>
</section>
</template>
<style>
.sponsor-section {
margin-bottom: 20px;
padding-bottom: 16px;
}
.sponsor-content {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.sponsor-item {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background-color: #B988D1;
color: var(--vscode-button-foreground);
border-radius: 4px;
text-decoration: none;
transition: transform 0.1s ease;
}
.sponsor-item:hover {
background-color: #8e5bbf;
transition: transform 0.1s ease;
}
.sponsor-item:active {
transform: scale(0.95);
transition: transform 0.1s ease;
}
</style>

View File

@ -0,0 +1,44 @@
<script setup lang="ts">
</script>
<template>
<section class="troubleshoot-section">
<div class="section-title">🔧 Troubleshooting</div>
<ul class="troubleshoot-list">
<li>
If the interface displays abnormally, try restarting VSCode or report an <a href="https://github.com/LSTM-Kirigaya/openmcp-client/issues" target="_blank">Issue</a>.
</li>
<li>
For more questions, refer to the <a href="https://openmcp.kirigaya.cn/" target="_blank">official
documentation</a> or join the community.</li>
<li>Contant Jinhui via <a href="mailto:1193466151@qq.com">1193466151@qq.com</a> to seek technical consultation or business cooperation.</li>
</ul>
</section>
</template>
<style>
.troubleshoot-section {
margin-bottom: 20px;
padding-bottom: 16px;
}
.troubleshoot-list {
list-style: disc inside;
padding-left: 18px;
color: var(--foreground, #444);
}
.troubleshoot-list li {
margin-bottom: 0.75rem;
}
.troubleshoot-list a {
color: #B988D1;
text-decoration: underline;
}
.troubleshoot-list a:hover {
color: #8e5bbf;
}
</style>

View File

@ -0,0 +1,73 @@
<script setup lang="ts">
const props = defineProps({
version: {
type: String,
default: '0.1.9'
},
changelogs: {
type: Array as () => string[],
default: () => [
'Brand new VSCode WebView adaptation, smoother experience',
'Support for AI Mock, toolchain visualization, and automatic topology detection',
'Multi-model concurrency, enhanced plugin capabilities'
]
}
});
</script>
<template>
<section class="news-section">
<div class="news-title">
<span>📣 What's New in <span class="highlight">{{ props.version }}</span></span>
</div>
<div class="news-content">
<ul class="news-list">
<li v-for="(log, index) in props.changelogs" :key="index">
<span class="news-badge">{{ '/' }}</span>
<span>{{ log }}</span>
</li>
</ul>
<br>
<a class="release-link" href="https://openmcp.kirigaya.cn/preview/changelog.html" target="_blank"
rel="noopener">View History Changelog </a>
</div>
</section>
</template>
<style>
.news-section {
margin-bottom: 20px;
}
.news-title {
display: flex;
align-items: center;
font-size: 1.5rem;
font-weight: bold;
color: #B988D1;
margin-bottom: 10px;
}
.news-title .logo {
width: 38px;
height: 38px;
margin-right: 12px;
vertical-align: middle;
}
.news-title .highlight {
color: #B988D1;
}
.news-content {
border-radius: 12px;
padding: 2rem;
margin: 2rem 0;
background-color: var(--vscode-sideBar-background);
box-shadow: 0 2px 8px var(--vscode-widget-shadow);
}
.news-content li {
margin-bottom: 0.7rem;
}
</style>

151
news/src/css/vscode.css Normal file
View File

@ -0,0 +1,151 @@
:root {
--font-monospace-family: var(--vscode-editor-font-family);
--font-monospace-weight: var(--vscode-editor-font-weight);
--font-monospace-size: var(--vscode-editor-font-size);
--link-foreground: var(--vscode-textLink-foreground);
--link-active: var(--vscode-textLink-activeForeground);
/* UI & Control */
--input-active-background: var(--vscode-input-background);
--input-active-border: var(--vscode-focusBorder);
--input-active-foreground: var(--vscode-input-foreground);
--input-error-background: var(--vscode-inputValidation-errorBackground);
--input-error-border: var(--vscode-inputValidation-errorBorder);
--input-error-foreground: var(--vscode-inputValidation-errorForeground);
--input-foreground: var(--vscode-input-foreground);
--input-background: var(--vscode-input-background);
--input-border: var(--vscode-input-border);
--input-hover: var(--vscode-input-background);
--input-placeholder: var(--vscode-input-placeholderForeground);
--input-radius: 0px;
--scrollbar-background: var(--vscode-scrollbarSlider-background);
--scrollbar-hover: var(--vscode-scrollbarSlider-hoverBackground);
--scrollbar-active: var(--vscode-scrollbarSlider-activeBackground);
/* Window */
--title-bar: #1f1f1f;
--title-color: #fff;
--foreground: var(--vscode-editor-foreground);
--background: var(--vscode-editor-background);
--label: rgb(189, 189, 189);
--shadow: #000;
--border: var(--vscode-input-border);
--window-button-hover: rgba(255,255,255,0.1);
--window-button-active: rgba(255,255,255,0.2);
--window-blur-background: rgba(0,0,0,0.25);
--window-title-foreground: var(--foreground);
--window-background: var(--sidebar);
--window-border: transparent;
--window-radius: 0px;
/* Sidebar */
--sidebar: var(--vscode-sideBar-background);
--sidebar-border: var(--vscode-sideBar-border);
--sidebar-min-width: 280px;
--sidebar-item-text: var(--vscode-list-inactiveSelectionForeground);
--sidebar-item-border: var(--vscode-input-border);
--sidebar-item-background: var(--sidebar);
--sidebar-item-selected: var(--vscode-list-inactiveSelectionBackground);
--sidebar-item-hover: var(--vscode-list-hoverBackground);
--sidebar-item-max-height: 40px;
--sidebar-item-radix-background: var(--vscode-breadcrumb-background);
--sidebar-group-text: var(--vscode-sideBarSectionHeader-foreground);
--sidebar-group-border: var(--vscode-sideBarSectionHeader-border);
--sidebar-group-background: var(--vscode-sideBarSectionHeader-background);
/* Labels */
--signalSize-background: rgba(0,0,0,0.5);
--signalSize-border: rgba(255,255,255,0.2);
--signalSize-color: var(--foreground);
/* Color Picker */
--picker-swatch-size: 15px;
--picker-swatch-cols: 8;
--picker-background: var(--vscode-breadcrumbPicker-background);
--picker-border: var(--vscode-dropdown-border);
/* Search */
--search-background: var(--vscode-quickInput-background);
--search-border: var(--border);
--search-panel-background: transparent;
--search-panel-border: var(--vscode-pickerGroup-border);
--search-panel-text: var(--vscode-quickInput-foreground);
--search-label: var(--foreground);
--search-selected-background: var(--vscode-list-inactiveSelectionBackground);
/* Properties */
--properties-background: var(--vscode-breadcrumb-background);
--properties-border: var(--border);
/* Navbar */
--navBar-background: var(--sidebar);
--navBar-height: 32px;
--navBar-button: transparent;
--navBar-button-text: var(--foreground);
--navBar-group-background: var(--background);
--navBar-preview-background: var(--vscode-scrollbarSlider-background);
--navBar-slider-border: var(--foreground);
/* Buttons */
--button: var(--vscode-button-background);
--button-text: var(--vscode-button-foreground);
--button-hover: var(--vscode-button-hoverBackground);
--button-active: var(--vscode-button-hoverBackground);
--button-disabled: var(--vscode-activityBar-background);
--button-disabled-text: var(--vscode-activityBar-inactiveForeground);
/* Grid Lines */
--grid-dash: 2;
--grid-space: 4;
--grid-line: var(--vscode-editorIndentGuide-background);
--grid-tick: var(--vscode-editorIndentGuide-activeBackground);
/* Cursor */
--cursor: var(--vscode-editorCursor-foreground);
--cursor-ghost: rgba(255, 255, 255, 0.2);
--cursor-width: 2;
/* X-Axis */
--axis-height: 38px;
--axis-line: var(--border);
--axis-background: var(--vscode-sideBar-background);
--axis-foreground: var(--foreground);
/* Signals */
--signal-highlight: var(--vscode-list-inactiveSelectionBackground);
/* Colors */
--accent: var(--vscode-button-background);
--accent-dim: #234175;
--accent-bright: #24c5f7;
--accent-hover: var(--vscode-button-hoverBackground);
--vscode-font-size: 13px;
--color-red: #ff5252;
--color-pink: #ff4081;
--color-purple: #e040fb;
--color-deepPurple: #7c4dff;
--color-indigo: #536dfe;
--color-blue: #448aff;
--color-lightBlue: #40c4ff;
--color-cyan: #18ffff;
--color-teal: #64ffda;
--color-green: #69f0ae;
--color-lightGreen: #b2ff59;
--color-lime: #eeff41;
--color-yellow: #ffff00;
--color-amber: #ffd740;
--color-orange: #ffab40;
--color-deepOrange: #ff6e40;
/* Settings */
--settings-action-background: var(--background);
}

48
news/src/data.json Normal file
View File

@ -0,0 +1,48 @@
{
"version": "0.1.9",
"changelogs": [
"Add mook functionality: Automatically fill in test tool form data using random seeds or AI generation.",
"Add tool self-check functionality: Under openmcp's tool, click 'Tool Self-Check' on the right side of 'Tool Module' to enter self-check mode. In this mode, users can define the topological order of tool execution and perform automatic detection in one go.",
"Fix issue #44: Complete platform adaptation for link redirection.",
"Fix issue #36: Ensure successful startup when not opening a folder.",
"Fix issue #45: Array type parameters are not supported.",
"Fix the issue of abnormal dialog styles when pasting multi-line conversations into the dialog box."
],
"contributors": [
{
"username": "LSTM-Kirigaya",
"avatarUrl": "https://avatars.githubusercontent.com/u/59416203?v=4",
"homeUrl": "https://github.com/LSTM-Kirigaya"
},
{
"username": "li1553770945",
"avatarUrl": "https://avatars.githubusercontent.com/u/55867654?v=4",
"homeUrl": "https://github.com/li1553770945"
},
{
"username": "STUzhy",
"avatarUrl": "https://avatars.githubusercontent.com/u/129645384?v=4",
"homeUrl": "https://github.com/STUzhy"
},
{
"username": "appli456",
"avatarUrl": "https://avatars.githubusercontent.com/u/8943691?v=4",
"homeUrl": "https://github.com/appli456"
},
{
"username": "cybermanhao",
"avatarUrl": "https://avatars.githubusercontent.com/u/37235140?v=4",
"homeUrl": "https://github.com/cybermanhao"
},
{
"username": "ArcStellar2025",
"avatarUrl": "https://avatars.githubusercontent.com/u/115577936?v=4",
"homeUrl": "https://github.com/ArcStellar2025"
},
{
"username": "ZYD045692",
"avatarUrl": "https://avatars.githubusercontent.com/u/206822796?v=4",
"homeUrl": "https://github.com/ZYD045692"
}
]
}

5
news/src/main.ts Normal file
View File

@ -0,0 +1,5 @@
import './css/vscode.css';
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app')

12
news/tsconfig.app.json Normal file
View File

@ -0,0 +1,12 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"paths": {
"@/*": ["./src/*"]
}
}
}

11
news/tsconfig.json Normal file
View File

@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}

19
news/tsconfig.node.json Normal file
View File

@ -0,0 +1,19 @@
{
"extends": "@tsconfig/node22/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*",
"eslint.config.*"
],
"compilerOptions": {
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}

20
news/vite.config.ts Normal file
View File

@ -0,0 +1,20 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import { viteSingleFile } from 'vite-plugin-singlefile'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
viteSingleFile(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})

18840
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,9 @@
"name": "openmcp", "name": "openmcp",
"displayName": "OpenMCP", "displayName": "OpenMCP",
"description": "An all in one MCP Client/TestTool", "description": "An all in one MCP Client/TestTool",
"version": "0.1.0", "version": "0.1.9",
"publisher": "kirigaya", "publisher": "kirigaya",
"private": true,
"author": { "author": {
"name": "kirigaya", "name": "kirigaya",
"email": "1193466151@qq.com" "email": "1193466151@qq.com"
@ -19,13 +20,16 @@
"Other" "Other"
], ],
"activationEvents": [], "activationEvents": [],
"main": "./dist/extension.js", "main": "./dist/extension.cjs.js",
"icon": "icons/openmcp.png", "icon": "icons/openmcp.png",
"contributes": { "contributes": {
"configuration": {
"properties": {}
},
"commands": [ "commands": [
{ {
"command": "openmcp.showOpenMCP", "command": "openmcp.showOpenMCP",
"title": "展示 OpenMCP", "title": "%openmcp.showOpenMCP.title%",
"category": "openmcp", "category": "openmcp",
"icon": { "icon": {
"light": "./icons/light/protocol.svg", "light": "./icons/light/protocol.svg",
@ -34,7 +38,7 @@
}, },
{ {
"command": "openmcp.sidebar.workspace-connection.revealWebviewPanel", "command": "openmcp.sidebar.workspace-connection.revealWebviewPanel",
"title": "连接", "title": "%openmcp.sidebar.workspace-connection.revealWebviewPanel.title%",
"category": "openmcp", "category": "openmcp",
"icon": { "icon": {
"light": "./icons/light/protocol.svg", "light": "./icons/light/protocol.svg",
@ -43,31 +47,31 @@
}, },
{ {
"command": "openmcp.sidebar.workspace-connection.deleteConnection", "command": "openmcp.sidebar.workspace-connection.deleteConnection",
"title": "删除连接", "title": "%openmcp.sidebar.workspace-connection.deleteConnection.title%",
"category": "openmcp", "category": "openmcp",
"icon": "$(trash)" "icon": "$(trash)"
}, },
{ {
"command": "openmcp.sidebar.workspace-connection.refresh", "command": "openmcp.sidebar.workspace-connection.refresh",
"title": "刷新", "title": "%openmcp.sidebar.workspace-connection.refresh.title%",
"category": "openmcp", "category": "openmcp",
"icon": "$(refresh)" "icon": "$(refresh)"
}, },
{ {
"command": "openmcp.sidebar.workspace-connection.addConnection", "command": "openmcp.sidebar.workspace-connection.addConnection",
"title": "添加连接", "title": "%openmcp.sidebar.workspace-connection.addConnection.title%",
"category": "openmcp", "category": "openmcp",
"icon": "$(add)" "icon": "$(add)"
}, },
{ {
"command": "openmcp.sidebar.workspace-connection.openConfiguration", "command": "openmcp.sidebar.workspace-connection.openConfiguration",
"title": "打开配置", "title": "%openmcp.sidebar.workspace-connection.openConfiguration.title%",
"category": "openmcp", "category": "openmcp",
"icon": "$(gear)" "icon": "$(gear)"
}, },
{ {
"command": "openmcp.sidebar.installed-connection.revealWebviewPanel", "command": "openmcp.sidebar.installed-connection.revealWebviewPanel",
"title": "连接", "title": "%openmcp.sidebar.installed-connection.revealWebviewPanel.title%",
"category": "openmcp", "category": "openmcp",
"icon": { "icon": {
"light": "./icons/light/protocol.svg", "light": "./icons/light/protocol.svg",
@ -76,31 +80,37 @@
}, },
{ {
"command": "openmcp.sidebar.installed-connection.deleteConnection", "command": "openmcp.sidebar.installed-connection.deleteConnection",
"title": "删除连接", "title": "%openmcp.sidebar.installed-connection.deleteConnection.title%",
"category": "openmcp", "category": "openmcp",
"icon": "$(trash)" "icon": "$(trash)"
}, },
{ {
"command": "openmcp.sidebar.installed-connection.refresh", "command": "openmcp.sidebar.installed-connection.refresh",
"title": "刷新", "title": "%openmcp.sidebar.installed-connection.refresh.title%",
"category": "openmcp", "category": "openmcp",
"icon": "$(refresh)" "icon": "$(refresh)"
}, },
{ {
"command": "openmcp.sidebar.installed-connection.addConnection", "command": "openmcp.sidebar.installed-connection.addConnection",
"title": "添加连接", "title": "%openmcp.sidebar.installed-connection.addConnection.title%",
"category": "openmcp", "category": "openmcp",
"icon": "$(add)" "icon": "$(add)"
}, },
{ {
"command": "openmcp.sidebar.installed-connection.openConfiguration", "command": "openmcp.sidebar.installed-connection.openConfiguration",
"title": "打开配置", "title": "%openmcp.sidebar.installed-connection.openConfiguration.title%",
"category": "openmcp", "category": "openmcp",
"icon": "$(gear)" "icon": "$(gear)"
}, },
{ {
"command": "openmcp.hook.test-ocr", "command": "openmcp.hook.test-ocr",
"title": "测试 OCR", "title": "%openmcp.hook.test-ocr.title%",
"category": "openmcp",
"icon": "$(test)"
},
{
"command": "openmcp.hook.test-news",
"title": "%openmcp.hook.test-news.title%",
"category": "openmcp", "category": "openmcp",
"icon": "$(test)" "icon": "$(test)"
} }
@ -194,19 +204,19 @@
{ {
"id": "openmcp.sidebar.workspace-connection", "id": "openmcp.sidebar.workspace-connection",
"icon": "./icons/protocol.svg", "icon": "./icons/protocol.svg",
"name": "MCP 连接 (工作区)", "name": "%openmcp.sidebar.workspace-connection.view.title%",
"type": "tree" "type": "tree"
}, },
{ {
"id": "openmcp.sidebar.installed-connection", "id": "openmcp.sidebar.installed-connection",
"icon": "./icons/protocol.svg", "icon": "./icons/protocol.svg",
"name": "安装的 MCP 服务器", "name": "%openmcp.sidebar.installed-connection.view.title%",
"type": "tree" "type": "tree"
}, },
{ {
"id": "openmcp.sidebar.help", "id": "openmcp.sidebar.help",
"icon": "./icons/protocol.svg", "icon": "./icons/protocol.svg",
"name": "入门与帮助", "name": "%openmcp.sidebar.help.view.title%",
"type": "tree" "type": "tree"
} }
] ]
@ -214,50 +224,79 @@
}, },
"workspaces": [ "workspaces": [
"service", "service",
"renderer", "renderer"
"software"
], ],
"scripts": { "scripts": {
"dev": "turbo dev --filter=!@openmcp/electron", "setup": "yarn install && yarn prepare:ocr",
"dev:electron": "turbo dev --filter=@openmcp/electron", "serve": "turbo serve",
"dev:all": "turbo dev", "build": "turbo build && tsc -p ./ && node esbuild.config.js",
"build": "turbo build --filter=!@openmcp/electron", "build:plugin": "yarn build && tsc && vsce package --allow-package-all-secrets",
"build:electron": "turbo build --filter=@openmcp/electron", "vscode:prepublish": "node esbuild.config.js",
"build:all": "turbo build",
"vscode:prepublish": "webpack --mode production",
"compile": "tsc -p ./", "compile": "tsc -p ./",
"watch": "tsc -watch -p ./", "watch": "tsc -watch -p ./",
"pretest": "npm run compile && npm run lint", "pretest": "yarn build",
"lint": "eslint src --ext ts", "lint": "eslint src --ext ts",
"test": "node ./out/test/runTest.js", "test": "node ./dist/test/e2e/runTest.js",
"prepare:ocr": "webpack --config webpack/webpack.tesseract.js", "prepare:ocr": "rollup -c rollup.tesseract.js --bundleConfigAsCjs",
"build:task-loop": "webpack --config webpack/webpack.task-loop.js" "build:news": "npx tsx scripts/update-news-data.mts",
"build:task-loop": "npx vite build --config renderer/vite.config.task-loop.mjs && node renderer/scripts/task-loop.build.mjs"
}, },
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.10.2", "@modelcontextprotocol/sdk": "^1.12.1",
"@openmcp/service": "*",
"@seald-io/nedb": "^4.1.1", "@seald-io/nedb": "^4.1.1",
"axios": "^1.7.7", "@types/glob": "^7.2.0",
"@types/node-fetch": "^2.6.4",
"abort-controller": "^3.0.0",
"agentkeepalive": "^4.2.1",
"axios": "^1.9.0",
"bson": "^6.8.0", "bson": "^6.8.0",
"openai": "^4.93.0", "form-data-encoder": "^1.7.2",
"formdata-node": "^4.3.2",
"glob": "^7.2.3",
"https-proxy-agent": "^7.0.6",
"mocha": "^10.8.2",
"openai": "^5.0.1",
"pako": "^2.1.0", "pako": "^2.1.0",
"pkce-challenge": "^5.0.0",
"tesseract.js": "^6.0.1", "tesseract.js": "^6.0.1",
"tslib": "^2.8.1",
"uuid": "^11.1.0", "uuid": "^11.1.0",
"ws": "^8.18.1" "ws": "^8.18.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "16.x", "@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^28.0.5",
"@rollup/plugin-inject": "^5.0.5",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-typescript": "^12.1.2",
"@types/node": "^22.15.29",
"@types/pako": "^2.0.3", "@types/pako": "^2.0.3",
"@types/showdown": "^2.0.0", "@types/showdown": "^2.0.0",
"@types/sinon": "^17.0.4",
"@types/vscode": "^1.72.0", "@types/vscode": "^1.72.0",
"@vscode/test-cli": "^0.0.11",
"@vscode/test-electron": "^2.5.2",
"@vscode/vsce": "^3.5.0",
"copy-webpack-plugin": "^13.0.0", "copy-webpack-plugin": "^13.0.0",
"esbuild": "^0.25.5",
"fork-ts-checker-webpack-plugin": "^9.1.0", "fork-ts-checker-webpack-plugin": "^9.1.0",
"null-loader": "^4.0.1", "null-loader": "^4.0.1",
"ompipe": "^1.0.2",
"rollup": "^4.43.0",
"rollup-plugin-copy": "^3.5.0",
"rollup-plugin-visualizer": "^6.0.1",
"sinon": "^21.0.0",
"ts-loader": "^9.5.1", "ts-loader": "^9.5.1",
"turbo": "^2.5.3", "turbo": "^2.5.3",
"typescript": "^5.4.2", "typescript": "^5.4.2",
"vite": "^6.3.5",
"vite-plugin-dts": "^4.5.4",
"vite-plugin-static-copy": "^3.0.0",
"vite-plugin-vue-devtools": "^7.7.6",
"vue-tsc": "^2.2.10",
"webpack": "^5.99.5", "webpack": "^5.99.5",
"webpack-cli": "^5.1.4" "webpack-cli": "^5.1.4"
}, },
"packageManager": "npm@10.0.0" "packageManager": "yarn@1.22.22"
} }

18
package.nls.ja.json Normal file
View File

@ -0,0 +1,18 @@
{
"openmcp.showOpenMCP.title": "OpenMCP を表示",
"openmcp.sidebar.workspace-connection.revealWebviewPanel.title": "接続",
"openmcp.sidebar.workspace-connection.deleteConnection.title": "接続を削除",
"openmcp.sidebar.workspace-connection.refresh.title": "更新",
"openmcp.sidebar.workspace-connection.addConnection.title": "接続を追加",
"openmcp.sidebar.workspace-connection.openConfiguration.title": "設定を開く",
"openmcp.sidebar.installed-connection.revealWebviewPanel.title": "接続",
"openmcp.sidebar.installed-connection.deleteConnection.title": "接続を削除",
"openmcp.sidebar.installed-connection.refresh.title": "更新",
"openmcp.sidebar.installed-connection.addConnection.title": "接続を追加",
"openmcp.sidebar.installed-connection.openConfiguration.title": "設定を開く",
"openmcp.hook.test-ocr.title": "OCR をテスト",
"openmcp.sidebar.workspace-connection.view.title": "MCP 接続(ワークスペース)",
"openmcp.sidebar.installed-connection.view.title": "インストール済み MCP サーバー",
"openmcp.sidebar.help.view.title": "はじめに・ヘルプ",
"openmcp.hook.test-news.title": "テストニュースレポート"
}

18
package.nls.json Normal file
View File

@ -0,0 +1,18 @@
{
"openmcp.showOpenMCP.title": "Show OpenMCP",
"openmcp.sidebar.workspace-connection.revealWebviewPanel.title": "Connect",
"openmcp.sidebar.workspace-connection.deleteConnection.title": "Delete Connection",
"openmcp.sidebar.workspace-connection.refresh.title": "Refresh",
"openmcp.sidebar.workspace-connection.addConnection.title": "Add Connection",
"openmcp.sidebar.workspace-connection.openConfiguration.title": "Open Configuration",
"openmcp.sidebar.installed-connection.revealWebviewPanel.title": "Connect",
"openmcp.sidebar.installed-connection.deleteConnection.title": "Delete Connection",
"openmcp.sidebar.installed-connection.refresh.title": "Refresh",
"openmcp.sidebar.installed-connection.addConnection.title": "Add Connection",
"openmcp.sidebar.installed-connection.openConfiguration.title": "Open Configuration",
"openmcp.hook.test-ocr.title": "Test OCR",
"openmcp.sidebar.workspace-connection.view.title": "MCP Connections (Workspace)",
"openmcp.sidebar.installed-connection.view.title": "Installed MCP Servers",
"openmcp.sidebar.help.view.title": "Getting Started & Help",
"openmcp.hook.test-news.title": "test news report"
}

18
package.nls.zh-cn.json Normal file
View File

@ -0,0 +1,18 @@
{
"openmcp.showOpenMCP.title": "展示 OpenMCP",
"openmcp.sidebar.workspace-connection.revealWebviewPanel.title": "连接",
"openmcp.sidebar.workspace-connection.deleteConnection.title": "删除连接",
"openmcp.sidebar.workspace-connection.refresh.title": "刷新",
"openmcp.sidebar.workspace-connection.addConnection.title": "添加连接",
"openmcp.sidebar.workspace-connection.openConfiguration.title": "打开配置",
"openmcp.sidebar.installed-connection.revealWebviewPanel.title": "连接",
"openmcp.sidebar.installed-connection.deleteConnection.title": "删除连接",
"openmcp.sidebar.installed-connection.refresh.title": "刷新",
"openmcp.sidebar.installed-connection.addConnection.title": "添加连接",
"openmcp.sidebar.installed-connection.openConfiguration.title": "打开配置",
"openmcp.hook.test-ocr.title": "测试 OCR",
"openmcp.sidebar.workspace-connection.view.title": "MCP 连接 (工作区)",
"openmcp.sidebar.installed-connection.view.title": "安装的 MCP 服务器",
"openmcp.sidebar.help.view.title": "入门与帮助",
"openmcp.hook.test-news.title": "测试新闻报告"
}

View File

@ -16,8 +16,17 @@
"type-check": "vue-tsc --build" "type-check": "vue-tsc --build"
}, },
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.18.6",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/theme-one-dark": "^6.1.2",
"@faker-js/faker": "^9.8.0",
"chalk": "^5.4.1",
"codemirror": "^6.0.1",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"d3": "^7.9.0",
"element-plus": "^2.9.9", "element-plus": "^2.9.9",
"elkjs": "^0.10.0",
"json-schema-faker": "^0.5.9",
"katex": "^0.16.21", "katex": "^0.16.21",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
@ -28,19 +37,37 @@
"uuid": "^11.1.0", "uuid": "^11.1.0",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-i18n": "^11.1.0", "vue-i18n": "^11.1.0",
"vue-router": "^4.5.0" "vue-router": "^4.5.0",
"xml2js": "^0.6.2"
}, },
"devDependencies": { "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", "@originjs/vite-plugin-commonjs": "^1.0.3",
"@rollup/pluginutils": "^5.1.4",
"@tsconfig/node22": "^22.0.1", "@tsconfig/node22": "^22.0.1",
"@types/d3": "^7.4.3",
"@types/markdown-it": "^14.1.2",
"@types/node": "^22.14.0", "@types/node": "^22.14.0",
"@types/prismjs": "^1.26.5", "@types/prismjs": "^1.26.5",
"@types/xml2js": "^0.4.14",
"@vitejs/plugin-vue": "^5.2.3", "@vitejs/plugin-vue": "^5.2.3",
"@vue/babel-plugin-jsx": "^1.4.0",
"@vue/devtools-core": "^7.7.6",
"@vue/tsconfig": "^0.7.0", "@vue/tsconfig": "^0.7.0",
"error-stack-parser-es": "^0.1.5",
"kolorist": "^1.8.0",
"npm-run-all2": "^7.0.2", "npm-run-all2": "^7.0.2",
"rollup": "^4.41.0",
"sirv": "^3.0.1",
"typescript": "~5.8.0", "typescript": "~5.8.0",
"vite": "^6.2.4", "vite": "^6.2.4",
"vite-plugin-vue-devtools": "^7.7.2", "vite-plugin-vue-devtools": "^7.7.2",
"vue-tsc": "^2.2.8" "vue-tsc": "^2.2.8"
} },
"main": "index.js",
"license": "MIT"
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View File

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 4870215 */ font-family: "iconfont"; /* Project id 4870215 */
src: url('iconfont.woff2?t=1746703816245') format('woff2'), src: url('iconfont.woff2?t=1751568095152') format('woff2'),
url('iconfont.woff?t=1746703816245') format('woff'), url('iconfont.woff?t=1751568095152') format('woff'),
url('iconfont.ttf?t=1746703816245') format('truetype'); url('iconfont.ttf?t=1751568095152') format('truetype');
} }
.iconfont { .iconfont {
@ -13,6 +13,38 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-pin:before {
content: "\e863";
}
.icon-serial:before {
content: "\e78f";
}
.icon-deploy:before {
content: "\e614";
}
.icon-suffix-xml:before {
content: "\e653";
}
.icon-MCP:before {
content: "\e63c";
}
.icon-wendang:before {
content: "\e61b";
}
.icon-proxy:before {
content: "\e723";
}
.icon-parallel:before {
content: "\e61d";
}
.icon-waiting:before { .icon-waiting:before {
content: "\e6d0"; content: "\e6d0";
} }

Binary file not shown.

View File

@ -0,0 +1,3 @@
# OpenRouter Icon Placeholder
# This would normally be an actual .ico file
# For now, using a placeholder that follows the naming convention

View File

@ -3,6 +3,15 @@
--main-color: #CB81DA; --main-color: #CB81DA;
--main-dark-color: #2D323B; --main-dark-color: #2D323B;
--main-light-color: rgba(203, 129, 218, 0.7); --main-light-color: rgba(203, 129, 218, 0.7);
--main-light-color-90: rgba(203, 129, 218, 0.9);
--main-light-color-80: rgba(203, 129, 218, 0.8);
--main-light-color-70: rgba(203, 129, 218, 0.7);
--main-light-color-60: rgba(203, 129, 218, 0.6);
--main-light-color-50: rgba(203, 129, 218, 0.5);
--main-light-color-40: rgba(203, 129, 218, 0.4);
--main-light-color-30: rgba(203, 129, 218, 0.3);
--main-light-color-20: rgba(203, 129, 218, 0.2);
--main-light-color-10: rgba(203, 129, 218, 0.1);
--sidebar-width: 330px; --sidebar-width: 330px;
--right-nav-width: 50px; --right-nav-width: 50px;
--time-scale-height: 30px; --time-scale-height: 30px;
@ -222,3 +231,10 @@ a {
.el-dropdown-menu__item:hover { .el-dropdown-menu__item:hover {
background-color: var(--background) !important; background-color: var(--background) !important;
} }
/* codemirror */
.ͼo,
.ͼo .cm-gutters {
background-color: transparent !important;
}

View File

@ -18,7 +18,7 @@ function recreateDir(filePath) {
const currentDir = process.cwd(); const currentDir = process.cwd();
// 确保上级目录的 openmcp-sdk 存在 // 确保上级目录的 openmcp-sdk 存在
const openMCPSdkPath = fsPath.join(currentDir, '..', 'openmcp-sdk') const openMCPSdkPath = fsPath.join(currentDir, '..', 'openmcp-sdk');
createDirIfExists(openMCPSdkPath); createDirIfExists(openMCPSdkPath);
const sdkRenderPath = fsPath.join(openMCPSdkPath, 'renderer'); const sdkRenderPath = fsPath.join(openMCPSdkPath, 'renderer');

View File

@ -0,0 +1,21 @@
import * as fs from 'node:fs';
const targetFile = './openmcp-sdk/task-loop.js';
if (fs.existsSync(targetFile)) {
let content = fs.readFileSync(targetFile, 'utf-8');
// Replace element-plus with ./tools.js
content = content.replace(/'element-plus'/g, "'./tools.mjs'");
content = content.replace(/"element-plus"/g, "\"./tools.mjs\"");
// content = content.replace(/const chalk = require\("chalk"\);/g, 'const chalk = require("chalk").default;');
// Replace define_window_default$number.performance with performance
content = content.replace(/define_window_default\$\d+\.performance/g, 'performance');
fs.writeFileSync(targetFile, content);
console.log('\x1b[32m✓ File processing completed\x1b[0m'); // Green color
} else {
console.log('Target file does not exist:', targetFile);
}

View File

@ -18,8 +18,7 @@ import MainPanel from '@/components/main-panel/index.vue';
import { setDefaultCss } from './hook/css'; import { setDefaultCss } from './hook/css';
import { greenLog, pinkLog } from './views/setting/util'; import { greenLog, pinkLog } from './views/setting/util';
import { useMessageBridge } from './api/message-bridge'; import { useMessageBridge } from './api/message-bridge';
import { initialise } from './views/connect/connection'; import { initialise } from './views/connect';
import { getPlatform } from './api/platform';
import Tour from '@/components/guide/tour.vue'; import Tour from '@/components/guide/tour.vue';
import { userHasReadGuide } from './components/guide/tour'; import { userHasReadGuide } from './components/guide/tour';
@ -37,7 +36,9 @@ bridge.addCommandListener('hello', data => {
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const useAuth = Boolean(import.meta.env.VITE_USE_AUTH); const useAuth = Boolean(import.meta.env.VITE_USE_AUTH !== "false");
console.log(import.meta.env.VITE_USE_AUTH, useAuth);
privilegeStatus.allow = !Boolean(useAuth); privilegeStatus.allow = !Boolean(useAuth);
onMounted(async () => { onMounted(async () => {
@ -61,7 +62,7 @@ onMounted(async () => {
return; return;
} }
document.addEventListener('click', () => { document?.addEventListener('click', () => {
Connection.showPanel = false; Connection.showPanel = false;
}); });

View File

@ -1,5 +1,6 @@
import { pinkLog, redLog } from '@/views/setting/util'; import { pinkLog, redLog } from '@/views/setting/util';
import { acquireVsCodeApi, electronApi, getPlatform } from './platform'; import { acquireVsCodeApi, electronApi, getPlatform } from './platform';
import { isReactive } from 'vue';
export interface VSCodeMessage { export interface VSCodeMessage {
command: string; command: string;
@ -38,22 +39,22 @@ export class MessageBridge {
switch (platform) { switch (platform) {
case 'vscode': case 'vscode':
this.setupVsCodeListener(); this.setupVsCodeListener();
pinkLog('当前模式: vscode'); pinkLog('current platform: vscode');
break; break;
case 'electron': case 'electron':
this.setupElectronListener(); this.setupElectronListener();
pinkLog('当前模式: electron'); pinkLog('current platform: electron');
break; break;
case 'nodejs': case 'nodejs':
this.setupNodejsListener(); this.setupNodejsListener();
pinkLog('当前模式: nodejs'); pinkLog('current platform: nodejs');
break; break;
case 'web': case 'web':
this.setupWebSocket(); this.setupWebSocket();
pinkLog('当前模式: web'); pinkLog('current platform: web');
break; break;
} }
} }
@ -77,6 +78,8 @@ export class MessageBridge {
throw new Error('setupSignature must be a string'); throw new Error('setupSignature must be a string');
} }
console.log(wsUrl);
this.ws = new WebSocket(wsUrl); this.ws = new WebSocket(wsUrl);
const ws = this.ws; const ws = this.ws;
@ -117,6 +120,7 @@ export class MessageBridge {
} }
public async awaitForWebsocket() { public async awaitForWebsocket() {
if (this.isConnected) { if (this.isConnected) {
return await this.isConnected; return await this.isConnected;
} }
@ -205,6 +209,21 @@ export class MessageBridge {
return () => commandHandlers.delete(wrapperCommandHandler); return () => commandHandlers.delete(wrapperCommandHandler);
} }
private deserializeReactiveData(data: any) {
if (isReactive(data)) {
return JSON.parse(JSON.stringify(data));
}
// 只对第一层进行遍历
for (const key in data) {
if (isReactive(data[key])) {
data[key] = JSON.parse(JSON.stringify(data[key]));
}
}
return data;
}
/** /**
* @description do as axios does * @description do as axios does
* @param command * @param command
@ -212,6 +231,7 @@ export class MessageBridge {
* @returns * @returns
*/ */
public commandRequest<T = any>(command: string, data?: ICommandRequestData): Promise<RestFulResponse<T>> { public commandRequest<T = any>(command: string, data?: ICommandRequestData): Promise<RestFulResponse<T>> {
return new Promise<RestFulResponse>((resolve, reject) => { return new Promise<RestFulResponse>((resolve, reject) => {
this.addCommandListener(command, (data) => { this.addCommandListener(command, (data) => {
resolve(data as RestFulResponse); resolve(data as RestFulResponse);
@ -219,7 +239,7 @@ export class MessageBridge {
this.postMessage({ this.postMessage({
command, command,
data data: this.deserializeReactiveData(data)
}); });
}); });
} }

View File

@ -58,7 +58,7 @@
</el-tour-step> </el-tour-step>
<el-tour-step <el-tour-step
:target="connectionSettingRef" :target="mcpClientAdapter.clients[0].connectionSettingRef"
:prev-button-props="{ children: '上一步' }" :prev-button-props="{ children: '上一步' }"
:next-button-props="{ children: '下一步' }" :next-button-props="{ children: '下一步' }"
:show-close="false" :show-close="false"
@ -78,7 +78,7 @@
</el-tour-step> </el-tour-step>
<el-tour-step <el-tour-step
:target="connectionLogRef" :target="mcpClientAdapter.clients[0].connectionLogRef"
:prev-button-props="{ children: '上一步' }" :prev-button-props="{ children: '上一步' }"
:next-button-props="{ children: '下一步' }" :next-button-props="{ children: '下一步' }"
:show-close="false" :show-close="false"
@ -92,6 +92,20 @@
</div> </div>
</el-tour-step> </el-tour-step>
<el-tour-step
:target="mcpServerAddRef"
:prev-button-props="{ children: '上一步' }"
:next-button-props="{ children: '下一步' }"
:show-close="false"
>
<template #header>
<TourTitle>调试</TourTitle>
</template>
<div class="tour-common-text">
你可以点击最右侧的加号来添加额外的 mcp 服务器让你当前正在调试的 agent 零成本快速获得额外的能力
</div>
</el-tour-step>
<el-tour-step <el-tour-step
target="#sidebar-debug" target="#sidebar-debug"
:prev-button-props="{ children: '上一步' }" :prev-button-props="{ children: '上一步' }"
@ -118,11 +132,35 @@
</template> </template>
<div class="tour-common-text"> <div class="tour-common-text">
我们目前提供了四种主要调试选项资源提词工具分别和 MCP 协议中的 resourcespromptstools 对应 我们目前提供了四种主要调试选项资源提词工具分别和 MCP 协议中的 resourcespromptstools 对应
</div>
</el-tour-step>
<el-tour-step
:target="welcomeRef"
:prev-button-props="{ children: '上一步' }"
:next-button-props="{ children: '下一步' }"
:show-close="false"
placement="right"
>
<template #header>
<TourTitle>调试</TourTitle>
</template>
<div class="tour-common-text">
交互测试则允许你直接将写好的 mcp 服务器放入大模型中直接做全链路测试从而更加获取更加真实的反馈和数据进而改进的你的 mcp 服务器 交互测试则允许你直接将写好的 mcp 服务器放入大模型中直接做全链路测试从而更加获取更加真实的反馈和数据进而改进的你的 mcp 服务器
</div>
</el-tour-step>
<br><br> <el-tour-step
:target="welcomeRef"
:prev-button-props="{ children: '上一步' }"
:next-button-props="{ children: '下一步' }"
:show-close="false"
placement="right"
>
<template #header>
<TourTitle>调试</TourTitle>
</template>
<div class="tour-common-text">
基于我们在 agent rl 方向的最佳实践我们后续还会推出更多的调试和数据集聚合制作选项请期待吧 基于我们在 agent rl 方向的最佳实践我们后续还会推出更多的调试和数据集聚合制作选项请期待吧
</div> </div>
</el-tour-step> </el-tour-step>
@ -210,10 +248,15 @@
:show-close="false" :show-close="false"
> >
<template #header> <template #header>
<TourTitle>🎉恭喜</TourTitle> <TourTitle>🎉 恭喜</TourTitle>
</template> </template>
<div class="tour-common-text"> <div class="tour-common-text">
🎉恭喜我的朋友现在的你已经是半个 mcp 专家了请充好一杯咖啡慢慢享用快乐的开发时间吧 🎉 恭喜我的朋友现在的你已经是半个 mcp 专家了请充好一杯咖啡慢慢享用快乐的开发时间吧
<br>
<br>
<a href="https://openmcp.kirigaya.cn/" target="_blank">OpenMCP 官方文档</a> 是我们的文档站点您在其中能找到非常完整的使用说明和案例教程
<br><br> <br><br>
@ -249,10 +292,11 @@ import TourTitle from './tour-title.vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { welcomeRef } from '@/views/debug/welcome'; import { welcomeRef } from '@/views/debug/welcome';
import { connectionLogRef, connectionSettingRef } from '@/views/connect/connection';
import { llmSettingRef } from '@/views/setting/api'; import { llmSettingRef } from '@/views/setting/api';
import { userHasReadGuide } from './tour'; import { userHasReadGuide } from './tour';
import { setTour } from '@/hook/setting'; import { setTour } from '@/hook/setting';
import { mcpClientAdapter } from '@/views/connect/core';
import { mcpServerAddRef } from '@/views/connect';
const openTour = ref(true); const openTour = ref(true);

View File

@ -6,12 +6,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, defineProps, type PropType } from 'vue'; import { defineProps, type PropType } from 'vue';
import { renderJson } from '../main-panel/chat/markdown/markdown'; import { renderJson } from '../main-panel/chat/markdown/markdown';
const props = defineProps({ const props = defineProps({
json: { json: {
type: Object as PropType<string | object | undefined>, type: Object as PropType<any>,
required: true required: true
} }
}); });

View File

@ -1,147 +1,203 @@
<template> <template>
<div class="k-input-object"> <div class="k-input-object">
<textarea ref="textareaRef" v-model="inputValue" class="k-input-object__textarea" <div :ref="el => editorContainer = el" class="k-input-object__editor"></div>
:class="{ 'is-invalid': isInvalid }" @input="handleInput" @blur="handleBlur" <div v-if="errorMessage" class="k-input-object__error">
@keydown="handleKeydown" {{ errorMessage }}
:placeholder="props.placeholder" </div>
></textarea>
</div>
<div v-if="errorMessage" class="k-input-object__error">
{{ errorMessage }}
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, ref, watch, nextTick } from 'vue'; import { ref, onMounted, watch, type PropType } from 'vue';
import { debounce } from 'lodash'; import { useI18n } from 'vue-i18n';
import { EditorView, basicSetup } from 'codemirror';
import type { Completion, CompletionContext } from "@codemirror/autocomplete"
import { jsonLanguage } from "@codemirror/lang-json"
export default defineComponent({ import { json } from '@codemirror/lang-json'
name: 'KInputObject', import { oneDark } from '@codemirror/theme-one-dark'
props: { import { debounce } from 'lodash'
modelValue: {
type: Object, const props = defineProps({
default: () => ({}) modelValue: {
}, type: Object,
placeholder: { default: () => ({})
type: String,
default: '请输入 JSON 对象'
},
debounceTime: {
type: Number,
default: 500
}
}, },
emits: ['update:modelValue', 'parse-error'], placeholder: {
setup(props, { emit }) { type: String,
const textareaRef = ref<HTMLTextAreaElement | null>(null) default: '请输入 JSON 对象'
const inputValue = ref<string>(JSON.stringify(props.modelValue, null, 2)) },
const isInvalid = ref<boolean>(false) debounceTime: {
const errorMessage = ref<string>('') type: Number,
default: 500
// },
const debouncedParse = debounce((value: string) => { schema: {
if (value.trim() === '') { type: Object as PropType<{
errorMessage.value = ''; type?: string;
isInvalid.value = false; properties?: Record<string, {
emit('update:modelValue', undefined); type: string;
return; description?: string;
} default?: any;
try { enum?: any[];
const parsed = JSON.parse(value); }>;
isInvalid.value = false; required?: string[];
errorMessage.value = ''; }>,
emit('update:modelValue', parsed); default: () => ({})
} catch (error) {
isInvalid.value = true;
errorMessage.value = 'JSON 解析错误: ' + (error as Error).message;
emit('parse-error', error);
}
}, props.debounceTime)
const handleInput = () => {
debouncedParse(inputValue.value)
}
const handleBlur = () => {
//
debouncedParse.flush()
}
// modelValue
watch(
() => props.modelValue,
(newVal) => {
const currentParsed = tryParse(inputValue.value)
if (!isDeepEqual(currentParsed, newVal)) {
inputValue.value = JSON.stringify(newVal, null, 2)
}
},
{ deep: true }
)
// JSON
const tryParse = (value: string): any => {
try {
return JSON.parse(value)
} catch {
return undefined
}
}
//
const isDeepEqual = (obj1: any, obj2: any): boolean => {
return JSON.stringify(obj1) === JSON.stringify(obj2)
}
const handleKeydown = (event: KeyboardEvent) => {
if (event.key === '{') {
event.preventDefault();
const start = textareaRef.value!.selectionStart;
const end = textareaRef.value!.selectionEnd;
const value = inputValue.value;
const newValue = value.substring(0, start) + '{\n \n}' + value.substring(end);
inputValue.value = newValue;
nextTick(() => {
textareaRef.value!.setSelectionRange(start + 2, start + 2);
});
} else if (event.key === '"') {
event.preventDefault();
const start = textareaRef.value!.selectionStart;
const end = textareaRef.value!.selectionEnd;
const value = inputValue.value;
const newValue = value.substring(0, start) + '""' + value.substring(end);
inputValue.value = newValue;
nextTick(() => {
textareaRef.value!.setSelectionRange(start + 1, start + 1);
});
} else if (event.key === 'Tab') {
event.preventDefault();
const start = textareaRef.value!.selectionStart;
const end = textareaRef.value!.selectionEnd;
const value = inputValue.value;
const newValue = value.substring(0, start) + ' ' + value.substring(end);
inputValue.value = newValue;
nextTick(() => {
textareaRef.value!.setSelectionRange(start + 1, start + 1);
});
} else if (event.key === 'Enter' && inputValue.value.trim() === '') {
event.preventDefault();
inputValue.value = '{}';
}
};
return {
textareaRef,
inputValue,
isInvalid,
errorMessage,
handleInput,
handleBlur,
handleKeydown,
props
}
} }
}) })
const emit = defineEmits(['update:modelValue', 'parse-error']);
const { t } = useI18n();
const editorContainer = ref<any>(null);
const editorView = ref<EditorView | null>(null);
const isInvalid = ref(false);
const errorMessage = ref('');
const inputValue = ref<string>(JSON.stringify(props.modelValue, null, 2));
//
const debouncedParse = debounce((value: string) => {
if (value.trim() === '') {
errorMessage.value = '';
isInvalid.value = false;
emit('update:modelValue', undefined);
return;
}
try {
const parsed = JSON.parse(value);
isInvalid.value = false;
errorMessage.value = '';
emit('update:modelValue', parsed);
} catch (error) {
isInvalid.value = true;
errorMessage.value = t('error-parse-json') + (error as Error).message;
emit('parse-error', error);
}
}, props.debounceTime);
onMounted(() => {
if (editorContainer.value) {
const extensions = [
basicSetup,
json(),
oneDark,
EditorView.updateListener.of(update => {
if (update.docChanged) {
const value = update.state.doc.toString()
debouncedParse(value)
}
})
]
// schema
if (Object.keys(props.schema).length > 0) {
extensions.push(
jsonLanguage.data.of({
autocomplete: getJsonCompletion(props.schema)
})
)
}
editorView.value = new EditorView({
doc: JSON.stringify(props.modelValue, null, 2),
extensions,
parent: editorContainer.value
})
}
})
//
function getJsonCompletion(schema: any) {
return (context: CompletionContext) => {
//
const charBefore = context.state.sliceDoc(context.pos - 1, context.pos)
if (/[,.{}[\]:]/.test(charBefore)) return null
const word = context.matchBefore(/\w*/)
if (!word) return null
//
const state = context.state
const pos = context.pos
const line = state.doc.lineAt(pos)
const textBefore = line.text.slice(0, pos - line.from)
//
const quoteCount = (textBefore.match(/"/g) || []).length
if (quoteCount % 2 !== 0) return null
const completions: Completion[] = []
//
if (schema.properties) {
Object.entries(schema.properties).forEach(([key, value]) => {
completions.push({
label: key,
type: "property",
apply: `"${key}": ${getDefaultValue(value as any)}`
})
})
}
return {
from: word.from,
options: completions,
validFor: /^\w*$/
}
}
}
//
function getDefaultValue(property: any): string {
if (property.default !== undefined) {
return JSON.stringify(property.default)
}
switch (property.type) {
case 'string': return '""'
case 'number': return '0'
case 'boolean': return 'false'
case 'object': return '{}'
case 'array': return '[]'
default: return 'null'
}
}
watch(
() => props.modelValue,
(newVal) => {
//
const currentContent = editorView.value?.state.doc.toString() ?? '';
const newContent = JSON.stringify(newVal ?? {}, null, 2);
if (currentContent !== newContent && editorView.value) {
editorView.value.dispatch({
changes: {
from: 0,
to: editorView.value.state.doc.length,
insert: newContent
}
});
}
},
{ deep: true }
);
// JSON
const tryParse = (value: string): any => {
try {
return JSON.parse(value)
} catch {
return undefined
}
}
//
const isDeepEqual = (obj1: any, obj2: any): boolean => {
return JSON.stringify(obj1) === JSON.stringify(obj2)
}
</script> </script>
<style scoped> <style scoped>
@ -151,6 +207,7 @@ export default defineComponent({
border-radius: .5em; border-radius: .5em;
margin-bottom: 15px; margin-bottom: 15px;
display: flex; display: flex;
flex-direction: column;
} }
.k-input-object__textarea { .k-input-object__textarea {
@ -174,6 +231,24 @@ export default defineComponent({
border-color: var(--el-color-error); border-color: var(--el-color-error);
} }
.k-input-object__error {
color: var(--el-color-error);
font-size: 12px;
margin-top: 4px;
}
.k-input-object__editor {
width: 100%;
border: 1px solid var(--el-border-color-light);
border-radius: 4px;
overflow: hidden;
background-color: var(--el-bg-color-overlay);
}
.k-input-object__editor.is-invalid {
border-color: var(--el-color-error);
}
.k-input-object__error { .k-input-object__error {
color: var(--el-color-error); color: var(--el-color-error);
font-size: 12px; font-size: 12px;

View File

@ -0,0 +1,10 @@
<template>
</template>
<script setup lang="ts">
</script>
<style>
</style>

View File

@ -1,5 +1,5 @@
import type { ToolCallContent, ToolItem } from "@/hook/type"; import type { InputSchema, ToolCallContent, ToolItem } from "@/hook/type";
import { type Ref, ref } from "vue"; import type { Ref } from "vue";
import type { OpenAI } from 'openai'; import type { OpenAI } from 'openai';
type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk; type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
@ -14,7 +14,9 @@ export enum MessageState {
ToolCall = 'tool call failed', ToolCall = 'tool call failed',
None = 'none', None = 'none',
Success = 'success', Success = 'success',
ParseJsonError = 'parse json error' ParseJsonError = 'parse json error',
NoToolFunction = 'no tool function',
InvalidXml = 'invalid xml',
} }
export interface IExtraInfo { export interface IExtraInfo {
@ -22,6 +24,7 @@ export interface IExtraInfo {
state: MessageState, state: MessageState,
serverName: string, serverName: string,
usage?: ChatCompletionChunk['usage']; usage?: ChatCompletionChunk['usage'];
enableXmlWrapper: boolean;
[key: string]: any; [key: string]: any;
} }
@ -47,36 +50,30 @@ export interface TextMessage {
export type ChatMessage = ToolMessage | TextMessage; export type ChatMessage = ToolMessage | TextMessage;
// 新增状态和工具数据 // 新增状态和工具数据
interface EnableToolItem { export interface EnableToolItem {
name: string; name: string;
description: string; description: string;
enabled: boolean; enabled: boolean;
inputSchema?: any; inputSchema: InputSchema;
} }
export interface ChatSetting { export interface ChatSetting {
modelIndex: number modelIndex?: number
systemPrompt: string systemPrompt: string
enableTools: EnableToolItem[] enableTools: EnableToolItem[]
temperature: number temperature: number
enableWebSearch: boolean enableWebSearch: boolean
contextLength: number contextLength: number
parallelToolCalls: boolean
enableXmlWrapper: boolean
} }
export interface ChatStorage { export interface ChatStorage {
messages: ChatMessage[] messages: ChatMessage[]
settings: ChatSetting settings: ChatSetting
} }
export interface ToolCall { export type ToolCall = OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta.ToolCall;
id?: string;
index?: number;
type: string;
function: {
name: string;
arguments: string;
}
}
interface PromptTextItem { interface PromptTextItem {
type: 'prompt' type: 'prompt'
@ -95,12 +92,10 @@ interface TextItem {
export type RichTextItem = PromptTextItem | ResourceTextItem | TextItem; export type RichTextItem = PromptTextItem | ResourceTextItem | TextItem;
export const allTools = ref<ToolItem[]>([]);
export interface ICommonRenderMessage { export interface ICommonRenderMessage {
role: 'user' | 'assistant/content'; role: 'user' | 'assistant/content';
content: string; content: string;
showJson?: Ref<boolean>; showJson?: any;
extraInfo: IExtraInfo; extraInfo: IExtraInfo;
} }
@ -109,42 +104,28 @@ export interface IToolRenderMessage {
content: string; content: string;
toolResults: ToolCallContent[][]; toolResults: ToolCallContent[][];
tool_calls: ToolCall[]; tool_calls: ToolCall[];
showJson?: Ref<boolean>; showJson?: any;
extraInfo: IExtraInfo; extraInfo: IExtraInfo;
} }
export type IRenderMessage = ICommonRenderMessage | IToolRenderMessage; export type IRenderMessage = ICommonRenderMessage | IToolRenderMessage;
export function getToolSchema(enableTools: EnableToolItem[]) { export function getToolSchema(enableTools: EnableToolItem[]): any[] {
const toolsSchema = []; const toolsSchema = [];
for (let i = 0; i < enableTools.length; i++) { for (let i = 0; i < enableTools.length; i++) {
const enableTool = enableTools[i]; const enableTool = enableTools[i];
if (enableTool.enabled) { if (enableTool.enabled) {
toolsSchema.push({
if (enableTool.inputSchema) { type: 'function',
toolsSchema.push({ function: {
type: 'function', name: enableTool.name,
function: { description: enableTool.description || "",
name: enableTool.name, parameters: enableTool.inputSchema
description: enableTool.description || "", }
parameters: enableTool.inputSchema });
} }
}); }
} else {
const tool = allTools.value[i];
toolsSchema.push({
type: 'function',
function: {
name: tool.name,
description: tool.description || "",
parameters: tool.inputSchema
}
});
}
}
}
return toolsSchema; return toolsSchema;
} }

View File

@ -4,6 +4,7 @@
<div class="input-wrapper"> <div class="input-wrapper">
<KRichTextarea <KRichTextarea
:ref="el => editorRef = el"
:tabId="tabId" :tabId="tabId"
v-model="userInput" v-model="userInput"
:placeholder="t('enter-message-dot')" :placeholder="t('enter-message-dot')"
@ -43,6 +44,7 @@ const props = defineProps({
}); });
const emits = defineEmits(['update:scrollToBottom']); const emits = defineEmits(['update:scrollToBottom']);
const editorRef = ref<any>(null);
const tab = tabs.content[props.tabId]; const tab = tabs.content[props.tabId];
const tabStorage = tab.storage as ChatStorage; const tabStorage = tab.storage as ChatStorage;
@ -84,6 +86,7 @@ function clearErrorMessage(errorMessage: string) {
} }
function handleSend(newMessage?: string) { function handleSend(newMessage?: string) {
// //
const userMessage = newMessage || userInput.value; const userMessage = newMessage || userInput.value;
@ -98,8 +101,6 @@ function handleSend(newMessage?: string) {
loop.bindStreaming(streamingContent, streamingToolCalls); loop.bindStreaming(streamingContent, streamingToolCalls);
loop.registerOnError((error) => { loop.registerOnError((error) => {
console.log('error.msg');
console.log(error.msg);
const errorMessage = clearErrorMessage(error.msg); const errorMessage = clearErrorMessage(error.msg);
ElMessage.error(errorMessage); ElMessage.error(errorMessage);
@ -111,12 +112,11 @@ function handleSend(newMessage?: string) {
extraInfo: { extraInfo: {
created: Date.now(), created: Date.now(),
state: error.state, state: error.state,
serverName: llms[llmManager.currentModelIndex].id || 'unknown' serverName: llms[llmManager.currentModelIndex].id || 'unknown',
enableXmlWrapper: false
} }
}); });
} }
isLoading.value = false;
}); });
loop.registerOnChunk(() => { loop.registerOnChunk(() => {
@ -124,7 +124,6 @@ function handleSend(newMessage?: string) {
}); });
loop.registerOnDone(() => { loop.registerOnDone(() => {
isLoading.value = false;
scrollToBottom(); scrollToBottom();
}); });
@ -133,9 +132,16 @@ function handleSend(newMessage?: string) {
scrollToBottom(); scrollToBottom();
}); });
loop.start(tabStorage, userMessage); loop.start(tabStorage, userMessage).then(() => {
isLoading.value = false;
});
//
userInput.value = ''; userInput.value = '';
const editor = editorRef.value.editor;
if (editor) {
editor.innerHTML = '';
}
} }
function handleAbort() { function handleAbort() {

View File

@ -1,6 +1,6 @@
<template> <template>
<el-tooltip :content="t('context-length')" placement="top"> <el-tooltip :content="t('context-length')" placement="top" effect="light">
<div class="setting-button" @click="showContextLengthDialog = true"> <div class="setting-button width-30" @click="showContextLengthDialog = true">
<span class="iconfont icon-length"></span> <span class="iconfont icon-length"></span>
<span class="value-badge">{{ tabStorage.settings.contextLength }}</span> <span class="value-badge">{{ tabStorage.settings.contextLength }}</span>
</div> </div>
@ -10,7 +10,7 @@
<el-dialog v-model="showContextLengthDialog" :title="t('context-length') + ' ' + tabStorage.settings.contextLength" <el-dialog v-model="showContextLengthDialog" :title="t('context-length') + ' ' + tabStorage.settings.contextLength"
width="400px"> width="400px">
<div class="slider-container"> <div class="slider-container">
<el-slider v-model="tabStorage.settings.contextLength" :min="1" :max="99" :step="1" /> <el-slider v-model="tabStorage.settings.contextLength" :min="1" :max="500" :step="1" />
<div class="slider-tips"> <div class="slider-tips">
<span> 1: {{ t('single-dialog') }}</span> <span> 1: {{ t('single-dialog') }}</span>
<span> >1: {{ t('multi-dialog') }}</span> <span> >1: {{ t('multi-dialog') }}</span>
@ -39,4 +39,8 @@ const showContextLengthDialog = ref(false);
.icon-length { .icon-length {
font-size: 16px; font-size: 16px;
} }
.width-30 {
width: 30px;
}
</style> </style>

View File

@ -0,0 +1,210 @@
<template>
<el-tooltip :content="'导出 mcpconfig'" placement="top" effect="light">
<div class="setting-button" @click="toggleDialog">
<span class="iconfont icon-deploy">
</span>
</div>
</el-tooltip>
<el-dialog v-model="showDialog" width="800px">
<template #header>
<div>
<div class="export-file-input">
<span>{{ t('export-filename') }}</span>
<el-input
v-model="exportFileName"
style="max-width: 300px"
>
<template #append>.json</template>
</el-input>
<span class="how-to-use"
@click="gotoHowtoUse"
>
<span class="iconfont icon-info"></span>
<span>{{ t('how-to-use') }}</span>
</span>
</div>
</div>
</template>
<div class="tools-dialog-container">
<el-scrollbar height="400px" class="tools-list">
<div v-html="exportJson">
</div>
</el-scrollbar>
<!--
<el-scrollbar height="400px" class="tools-list">
<div v-html="exampleCode"></div>
</el-scrollbar> -->
</div>
<template #footer>
<el-button type="primary" @click="copyCode">{{ t('copy') }}</el-button>
<el-button type="primary" @click="exportCode">{{ t('export') }}</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed, inject, onMounted, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { type ChatStorage, type EnableToolItem, getToolSchema } from '../chat';
import { markdownToHtml } from '@/components/main-panel/chat/markdown/markdown';
import { llmManager, llms } from '@/views/setting/llm';
import { mcpClientAdapter } from '@/views/connect/core';
import { ElMessage } from 'element-plus';
import { useMessageBridge } from '@/api/message-bridge';
import { gotoWebsite } from '@/hook/util';
const { t, locale } = useI18n();
const showDialog = ref(false);
const exportJson = ref('');
const exportFileName = ref('mcpconfig');
// toggleDialog
const toggleDialog = () => {
showDialog.value = true;
};
const generateExportData = computed(() => {
const currentLLM = llms[llmManager.currentModelIndex];
const mcpServers = {} as any;
for (const client of mcpClientAdapter.clients) {
const option = client.connectOption;
const type = option.connectionType;
if (type === 'STDIO') {
mcpServers[client.name] = {
command: option.command,
args: option.args,
cwd: option.cwd,
description: "",
}
} else if (type === 'SSE') {
mcpServers[client.name] = {
type: 'sse',
url: option.url,
description: "",
}
} else {
mcpServers[client.name] = {
type: 'streamable_http',
url: option.url,
description: "",
}
}
}
const mcpconfig = {
version: "0.0.1",
namespace: "openmcp",
mcpServers,
defaultLLM: {
baseURL: currentLLM.baseUrl,
apiToken: currentLLM.userToken,
model: currentLLM.userModel
}
};
return JSON.stringify(mcpconfig, null, 2);
});
const innerMarkdownHtml = (code: string) => {
const rawCode = markdownToHtml(code);
const doc = new DOMParser().parseFromString(rawCode, 'text/html');
const pres = doc.querySelectorAll('pre');
if (pres.length < 2) {
return '';
}
const inner = pres[1].outerHTML;
return inner;
}
const exampleCode = computed(() => {
return innerMarkdownHtml(
'```typescript\n' +
`import { OmAgent } from 'openmcp-sdk/service/sdk';
const agent = new OmAgent();
agent.loadMcpConfig('/path/to/${exportFileName.value}.json');
const prompt = await agent.getPrompt('hacknews', { topn: '5' });
const res = await agent.ainvoke({ messages: prompt });
console.log('⚙️ Agent Response', res);
` +
'\n```'
);
});
const copyCode = async () => {
try {
await navigator.clipboard.writeText(generateExportData.value);
ElMessage.success(t('copy-success'));
} catch (error) {
ElMessage.error(t('copy-fail'));
}
}
const exportCode = async () => {
const bridge = useMessageBridge();
bridge.postMessage({
command: 'vscode/export-file',
data: {
filename: exportFileName.value,
content: generateExportData.value
}
})
}
const gotoHowtoUse = () => {
if (locale.value === 'zh') {
gotoWebsite('https://openmcp.kirigaya.cn/zh/sdk-tutorial/#%E4%BD%BF%E7%94%A8');
} else if (locale.value === 'ja') {
gotoWebsite('https://openmcp.kirigaya.cn/ja/sdk-tutorial/#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95');
} else {
gotoWebsite('https://openmcp.kirigaya.cn/sdk-tutorial/#usage');
}
}
onMounted(async () => {
exportJson.value = innerMarkdownHtml(
'```json\n' + generateExportData.value + '\n```'
);
});
</script>
<style scoped>
.export-file-input {
display: flex;
gap: 10px;
align-items: center;
}
.tools-list {
border-radius: .5em;
border: 1px solid var(--main-color);
padding: 5px;
}
.how-to-use {
margin-left: 10px;
font-size: 15px;
cursor: pointer;
background-color: var(--main-color);
color: #1e1e1e;
padding: 3px 5px;
border-radius: .3em;
}
</style>

View File

@ -1,5 +1,5 @@
<template> <template>
<el-tooltip :content="t('choose-model')" placement="top"> <el-tooltip :content="t('choose-model')" placement="top" effect="light">
<div class="setting-button" @click="showModelDialog = true"> <div class="setting-button" @click="showModelDialog = true">
<span class="iconfont icon-model"> <span class="iconfont icon-model">
{{ currentServerName }}/{{ currentModelName }} {{ currentServerName }}/{{ currentModelName }}
@ -64,4 +64,15 @@ const onRadioGroupChange = () => {
</script> </script>
<style></style> <style>
.setting-button:hover {
background: var(--main-light-color, #f0f8ff);
box-shadow: 0 2px 8px 0 rgba(64,158,255,0.08);
border-color: var(--el-color-primary-light-7, #c6e2ff);
}
.setting-button:active {
transform: scale(0.95);
}
</style>

View File

@ -0,0 +1,25 @@
<template>
<el-tooltip :content="t('parallel-tool-calls')" placement="top" effect="light">
<div class="setting-button" :class="{ 'active': tabStorage.settings.parallelToolCalls }" size="small"
@click="toggle">
<span class="iconfont icon-parallel"></span>
</div>
</el-tooltip>
</template>
<script setup lang="ts">
import { inject } from 'vue';
import { useI18n } from 'vue-i18n';
import type { ChatStorage } from '../chat';
const { t } = useI18n();
const tabStorage = inject('tabStorage') as ChatStorage;
const toggle = () => {
tabStorage.settings.parallelToolCalls = !tabStorage.settings.parallelToolCalls;
};
</script>
<style></style>

View File

@ -1,5 +1,5 @@
<template> <template>
<el-tooltip :content="t('prompts')" placement="top"> <el-tooltip :content="t('prompts')" placement="top" effect="light">
<div class="setting-button" @click="showChoosePrompt = true; saveCursorPosition();"> <div class="setting-button" @click="showChoosePrompt = true; saveCursorPosition();">
<span class="iconfont icon-chat"></span> <span class="iconfont icon-chat"></span>
</div> </div>
@ -70,7 +70,7 @@ async function whenGetPromptResponse(msg: PromptsGetResponse) {
return; return;
} }
const container = document.createElement('div'); const container = document?.createElement('div');
const promptChatItem = createApp(PromptChatItem, { const promptChatItem = createApp(PromptChatItem, {
messages: msg.messages messages: msg.messages
}); });
@ -88,7 +88,7 @@ async function whenGetPromptResponse(msg: PromptsGetResponse) {
} }
// //
const newRange = document.createRange(); const newRange = document?.createRange();
newRange.setStartAfter(firstElement); newRange.setStartAfter(firstElement);
newRange.collapse(true); newRange.collapse(true);
const selection = window.getSelection(); const selection = window.getSelection();

View File

@ -1,5 +1,5 @@
<template> <template>
<el-tooltip :content="t('resources')" placement="top"> <el-tooltip :content="t('resources')" placement="top" effect="light">
<div class="setting-button" @click="showChooseResource = true; saveCursorPosition();"> <div class="setting-button" @click="showChooseResource = true; saveCursorPosition();">
<span class="iconfont icon-file"></span> <span class="iconfont icon-file"></span>
</div> </div>
@ -32,7 +32,7 @@ import ResourceReader from '@/components/main-panel/resource/resouce-reader.vue'
import { ElMessage, ElTooltip, ElProgress, ElPopover } from 'element-plus'; import { ElMessage, ElTooltip, ElProgress, ElPopover } from 'element-plus';
import ResourceChatItem from '../resource-chat-item.vue'; import ResourceChatItem from '../resource-chat-item.vue';
import { useMessageBridge } from '@/api/message-bridge'; import { mcpClientAdapter } from '@/views/connect/core';
const { t } = useI18n(); const { t } = useI18n();
@ -60,8 +60,8 @@ function saveCursorPosition() {
async function handleResourceSelected(resource: Resources) { async function handleResourceSelected(resource: Resources) {
selectResource.value = undefined; selectResource.value = undefined;
const bridge = useMessageBridge(); const msg = await mcpClientAdapter.readResource(resource.uri);
const { code, msg } = await bridge.commandRequest('resources/read', { resourceUri: resource.uri });
if (msg) { if (msg) {
await whenGetResourceResponse(msg as ResourcesReadResponse); await whenGetResourceResponse(msg as ResourcesReadResponse);
} }
@ -82,7 +82,7 @@ async function whenGetResourceResponse(msg: ResourcesReadResponse) {
return; return;
} }
const container = document.createElement('div'); const container = document?.createElement('div');
const resourceChatItem = createApp(ResourceChatItem, { const resourceChatItem = createApp(ResourceChatItem, {
contents: msg.contents contents: msg.contents
}); });
@ -101,7 +101,7 @@ async function whenGetResourceResponse(msg: ResourcesReadResponse) {
editor.appendChild(firstElement); editor.appendChild(firstElement);
} }
const newRange = document.createRange(); const newRange = document?.createRange();
newRange.setStartAfter(firstElement); newRange.setStartAfter(firstElement);
newRange.collapse(true); newRange.collapse(true);
const selection = window.getSelection(); const selection = window.getSelection();
@ -123,4 +123,13 @@ async function whenGetResourceResponse(msg: ResourcesReadResponse) {
.icon-length { .icon-length {
font-size: 16px; font-size: 16px;
} }
.el-dialog .el-collapse-item__header {
background-color: transparent !important;
}
.el-dialog .el-collapse-item__wrap {
background-color: transparent !important;
}
</style> </style>

View File

@ -5,9 +5,11 @@
<ToolUse /> <ToolUse />
<Prompt /> <Prompt />
<Resource /> <Resource />
<Websearch /> <ParallelToolCalls />
<Temperature /> <Temperature />
<ContextLength /> <ContextLength />
<XmlWrapper />
<Export />
</div> </div>
</template> </template>
@ -22,9 +24,11 @@ import SystemPrompt from './system-prompt.vue';
import ToolUse from './tool-use.vue'; import ToolUse from './tool-use.vue';
import Prompt from './prompt.vue'; import Prompt from './prompt.vue';
import Resource from './resource.vue'; import Resource from './resource.vue';
import Websearch from './websearch.vue'; import ParallelToolCalls from './parallel-tool-calls.vue';
import Temperature from './temperature.vue'; import Temperature from './temperature.vue';
import ContextLength from './context-length.vue'; import ContextLength from './context-length.vue';
import XmlWrapper from './xml-wrapper.vue';
import Export from './export.vue';
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@ -57,9 +61,11 @@ if (!tabStorage.settings) {
modelIndex: llmManager.currentModelIndex, modelIndex: llmManager.currentModelIndex,
enableTools: [], enableTools: [],
enableWebSearch: false, enableWebSearch: false,
temperature: 0.7, temperature: 0.6,
contextLength: 20, contextLength: 100,
systemPrompt: '' systemPrompt: '',
enableXmlWrapper: false,
parallelToolCalls: true
} as ChatSetting; } as ChatSetting;
} }
@ -156,16 +162,15 @@ provide('tabStorage', tabStorage);
color: var(--el-text-color-secondary); color: var(--el-text-color-secondary);
} }
.tools-dialog-container .el-switch__core { .el-switch__core {
border: 1px solid var(--main-color) !important; border: 1px solid var(--main-color) !important;
} }
.el-switch .el-switch__action {
.tools-dialog-container .el-switch .el-switch__action {
background-color: var(--main-color); background-color: var(--main-color);
} }
.tools-dialog-container .el-switch.is-checked .el-switch__action { .el-switch.is-checked .el-switch__action {
background-color: var(--sidebar); background-color: var(--sidebar);
} }

View File

@ -22,8 +22,7 @@ export function getSystemPrompt(name: string) {
export async function saveSystemPrompts() { export async function saveSystemPrompts() {
const bridge = useMessageBridge(); const bridge = useMessageBridge();
const payload = JSON.parse(JSON.stringify(systemPrompts.value)); const res = await bridge.commandRequest('system-prompts/save', { prompts: systemPrompts.value });
const res = await bridge.commandRequest('system-prompts/save', { prompts: payload });
if (res.code === 200) { if (res.code === 200) {
pinkLog('system prompt 保存成功'); pinkLog('system prompt 保存成功');
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<el-tooltip :content="t('system-prompt')" placement="top"> <el-tooltip :content="t('system-prompt')" placement="top" effect="light">
<div class="setting-button" :class="{ 'active': hasSystemPrompt }" size="small" <div class="setting-button" :class="{ 'active': hasSystemPrompt }" size="small"
@click="showSystemPromptDialog = true"> @click="showSystemPromptDialog = true">
<span class="iconfont icon-prompt"></span> <span class="iconfont icon-prompt"></span>

View File

@ -1,6 +1,6 @@
<template> <template>
<el-tooltip :content="t('temperature-parameter')" placement="top"> <el-tooltip :content="t('temperature-parameter')" placement="top" effect="light">
<div class="setting-button" @click="showTemperatureSlider = true"> <div class="setting-button width-30" @click="showTemperatureSlider = true">
<span class="iconfont icon-temperature"></span> <span class="iconfont icon-temperature"></span>
<span class="value-badge">{{ tabStorage.settings.temperature.toFixed(1) }}</span> <span class="value-badge">{{ tabStorage.settings.temperature.toFixed(1) }}</span>
</div> </div>
@ -34,9 +34,13 @@ const showTemperatureSlider = ref(false);
</script> </script>
<style> <style scoped>
.icon-temperature { .icon-temperature {
font-size: 18px; font-size: 16px;
}
.width-30 {
width: 30px;
} }
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<el-tooltip :content="t('tool-use')" placement="top"> <el-tooltip :content="t('tool-use')" placement="top" effect="light">
<div class="setting-button" :class="{ 'active': availableToolsNum > 0 }" size="small" @click="toggleTools"> <div class="setting-button" :class="{ 'active': availableToolsNum > 0 }" size="small" @click="toggleTools">
<span class="iconfont icon-tool badge-outer"> <span class="iconfont icon-tool badge-outer">
<span class="badge-inner"> <span class="badge-inner">
@ -10,7 +10,19 @@
</el-tooltip> </el-tooltip>
<el-dialog v-model="showToolsDialog" title="工具管理" width="800px"> <el-dialog v-model="showToolsDialog" width="800px">
<template #header>
<div>
<span>{{ t('tool-manage') }}</span>
<el-tooltip :content="t('enable-xml-wrapper')" placement="top" effect="light">
<span class="xml-tag" :class="{
'active': tabStorage.settings.enableXmlWrapper
}" @click="tabStorage.settings.enableXmlWrapper = !tabStorage.settings.enableXmlWrapper">xml</span>
</el-tooltip>
</div>
</template>
<div class="tools-dialog-container"> <div class="tools-dialog-container">
<el-scrollbar height="400px" class="tools-list"> <el-scrollbar height="400px" class="tools-list">
<div v-for="(tool, index) in tabStorage.settings.enableTools" :key="index" class="tool-item"> <div v-for="(tool, index) in tabStorage.settings.enableTools" :key="index" class="tool-item">
@ -23,23 +35,27 @@
</el-scrollbar> </el-scrollbar>
<el-scrollbar height="400px" class="schema-viewer"> <el-scrollbar height="400px" class="schema-viewer">
<div v-html="activeToolsSchemaHTML"></div> <!-- 如果激活 xml 指令包裹则展示对应的 prompt -->
<div v-if="tabStorage.settings.enableXmlWrapper" v-html="activeToolsXmlPrompt" />
<!-- 如果是普通模式则展示普通的工具列表 -->
<div v-else v-html="activeToolsSchemaHTML" />
</el-scrollbar> </el-scrollbar>
</div> </div>
<template #footer> <template #footer>
<el-button type="primary" @click="enableAllTools">激活所有工具</el-button> <el-button type="primary" @click="enableAllTools">{{ t('enable-all-tools') }}</el-button>
<el-button type="danger" @click="disableAllTools">禁用所有工具</el-button> <el-button type="danger" @click="disableAllTools">{{ t('disable-all-tools') }}</el-button>
<el-button type="primary" @click="showToolsDialog = false">{{ t("cancel") }}</el-button> <el-button type="primary" @click="showToolsDialog = false">{{ t("cancel") }}</el-button>
</template> </template>
</el-dialog> </el-dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, inject, onMounted } from 'vue'; import { ref, computed, inject, onMounted, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { allTools, type ChatStorage, getToolSchema } from '../chat'; import { type ChatStorage, type EnableToolItem, getToolSchema } from '../chat';
import { markdownToHtml } from '@/components/main-panel/chat/markdown/markdown'; import { markdownToHtml } from '@/components/main-panel/chat/markdown/markdown';
import { useMessageBridge } from '@/api/message-bridge'; import { mcpClientAdapter } from '@/views/connect/core';
import { toolSchemaToPromptDescription } from '../../core/xml-wrapper';
const { t } = useI18n(); const { t } = useI18n();
@ -48,28 +64,34 @@ const tabStorage = inject('tabStorage') as ChatStorage;
const showToolsDialog = ref(false); const showToolsDialog = ref(false);
const availableToolsNum = computed(() => { const availableToolsNum = computed(() => {
return tabStorage.settings.enableTools.filter(tool => tool.enabled).length; return tabStorage.settings.enableTools.filter(tool => tool.enabled).length;
}); });
// toggleTools // toggleTools
const toggleTools = () => { const toggleTools = () => {
showToolsDialog.value = true; showToolsDialog.value = true;
}; };
const activeToolsSchemaHTML = computed(() => {
const toolsSchema = getToolSchema(tabStorage.settings.enableTools);
const jsonString = JSON.stringify(toolsSchema, null, 2);
return markdownToHtml( const activeToolsSchemaHTML = computed(() => {
"```json\n" + jsonString + "\n```" const toolsSchema = getToolSchema(tabStorage.settings.enableTools);
); const jsonString = JSON.stringify(toolsSchema, null, 2);
return markdownToHtml(
"```json\n" + jsonString + "\n```"
);
});
const activeToolsXmlPrompt = computed(() => {
const prompt = toolSchemaToPromptDescription(tabStorage.settings.enableTools);
return markdownToHtml(
"```markdown\n" + prompt + "\n```"
);
}); });
// - // -
const enableAllTools = () => { const enableAllTools = () => {
tabStorage.settings.enableTools.forEach(tool => { tabStorage.settings.enableTools.forEach(tool => {
tool.enabled = true; tool.enabled = true;
}); });
}; };
@ -81,22 +103,64 @@ const disableAllTools = () => {
}); });
}; };
onMounted(async () => { //
const bridge = useMessageBridge(); const updateToolsList = async () => {
const res = await bridge.commandRequest('tools/list'); // tool tabStorage.settings.enableTools
if (res.code === 200) { // enable
allTools.value = res.msg.tools || []; const disableToolNames = new Set<string>(
tabStorage.settings.enableTools = []; tabStorage.settings.enableTools
for (const tool of allTools.value) { .filter(tool => !tool.enabled)
tabStorage.settings.enableTools.push({ .map(tool => tool.name)
name: tool.name, );
description: tool.description,
enabled: true const newTools: EnableToolItem[] = [];
});
} for (const client of mcpClientAdapter.clients) {
const tools = await client.getTools({ cache: false });
if (tools) {
for (const tool of tools.values()) {
const enabled = !disableToolNames.has(tool.name);
newTools.push({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
enabled
});
}
}
} }
tabStorage.settings.enableTools = newTools;
}
onMounted(async () => {
await updateToolsList();
watch(() => mcpClientAdapter.refreshSignal.value, async () => {
await updateToolsList();
});
}); });
</script> </script>
<style></style> <style scoped>
.xml-tag {
margin-left: 10px;
border-radius: .5em;
padding: 2px 5px;
font-size: 12px;
font-weight: 900;
color: black;
background-color: var(--main-color);
opacity: 0.3;
transition: var(--animation-3s);
cursor: pointer;
}
.xml-tag.active {
opacity: 1;
transition: var(--animation-3s);
}
</style>

View File

@ -1,5 +1,5 @@
<template> <template>
<el-tooltip :content="t('websearch')" placement="top"> <el-tooltip :content="t('websearch')" placement="top" effect="light">
<div class="setting-button" :class="{ 'active': tabStorage.settings.enableWebSearch }" size="small" <div class="setting-button" :class="{ 'active': tabStorage.settings.enableWebSearch }" size="small"
@click="toggleWebSearch"> @click="toggleWebSearch">
<span class="iconfont icon-web"></span> <span class="iconfont icon-web"></span>

View File

@ -0,0 +1,25 @@
<template>
<el-tooltip :content="t('enable-xml-wrapper')" placement="top" effect="light">
<div class="setting-button" :class="{ 'active': tabStorage.settings.enableXmlWrapper }" size="small"
@click="toggle">
<span class="iconfont icon-suffix-xml"></span>
</div>
</el-tooltip>
</template>
<script setup lang="ts">
import { defineComponent, inject } from 'vue';
import type { ChatStorage } from '../chat';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const tabStorage = inject('tabStorage') as ChatStorage;
const toggle = () => {
tabStorage.settings.enableXmlWrapper = !tabStorage.settings.enableXmlWrapper;
};
</script>
<style></style>

View File

@ -1,5 +1,5 @@
<template> <template>
<el-tooltip :content="props.messages[0].content.text" placement="top"> <el-tooltip :content="props.messages[0].content.text" placement="top" effect="light">
<span class="chat-prompt-item" contenteditable="false"> <span class="chat-prompt-item" contenteditable="false">
<span class="iconfont icon-chat"></span> <span class="iconfont icon-chat"></span>
<span class="real-text">{{ props.messages[0].content.text }}</span> <span class="real-text">{{ props.messages[0].content.text }}</span>
@ -22,9 +22,6 @@ const props = defineProps({
<style> <style>
.chat-prompt-item { .chat-prompt-item {
max-width: 80px; max-width: 80px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-flex; display: inline-flex;
border-radius: .3em; border-radius: .3em;
align-items: center; align-items: center;
@ -34,9 +31,17 @@ const props = defineProps({
font-size: 12px; font-size: 12px;
margin-left: 3px; margin-left: 3px;
margin-right: 3px; margin-right: 3px;
user-select: none;
} }
.chat-prompt-item .iconfont { .chat-prompt-item .iconfont {
margin-right: 4px; margin-right: 4px;
} }
.chat-prompt-item .real-text {
max-width: 60px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<el-tooltip placement="top"> <el-tooltip placement="top" effect="light">
<template #content> <template #content>
<div class="resource-chat-item-tooltip"> <div class="resource-chat-item-tooltip">
<div v-for="(item, index) of toolRenderItems" :key="index"> <div v-for="(item, index) of toolRenderItems" :key="index">

View File

@ -117,6 +117,12 @@ function extractTextFromCollection(collection: HTMLCollection) {
const isComposing = ref(false); const isComposing = ref(false);
defineExpose({
editor,
handleBackspace,
handleInput,
});
function handleKeydown(event: KeyboardEvent) { function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Enter' && !event.shiftKey && !isComposing.value) { if (event.key === 'Enter' && !event.shiftKey && !isComposing.value) {
@ -181,7 +187,7 @@ function handlePaste(event: ClipboardEvent) {
if (selection && selection.rangeCount > 0) { if (selection && selection.rangeCount > 0) {
const range = selection.getRangeAt(0); const range = selection.getRangeAt(0);
range.deleteContents(); range.deleteContents();
const textNode = document.createTextNode(pastedText); const textNode = document?.createTextNode(pastedText);
range.insertNode(textNode); range.insertNode(textNode);
range.setStartAfter(textNode); range.setStartAfter(textNode);
range.collapse(true); range.collapse(true);
@ -228,6 +234,7 @@ function handleCompositionEnd() {
.rich-editor { .rich-editor {
min-height: 100px; min-height: 100px;
outline: none; outline: none;
white-space: pre-wrap;
} }
.rich-editor:empty::before { .rich-editor:empty::before {

View File

@ -1,16 +1,39 @@
import type { ToolCallContent, ToolCallResponse } from "@/hook/type"; import type { ToolCallContent, ToolCallResponse } from "@/hook/type";
import { callTool } from "../../tool/tools";
import { MessageState, type ToolCall } from "../chat-box/chat"; import { MessageState, type ToolCall } from "../chat-box/chat";
import { mcpClientAdapter } from "@/views/connect/core";
import type { BasicLlmDescription } from "@/views/setting/llm";
import type OpenAI from "openai";
export interface TaskLoopChatOption {
id?: string
proxyServer?: string
enableXmlWrapper?: boolean
}
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & TaskLoopChatOption;
export interface ToolCallResult { export interface ToolCallResult {
state: MessageState; state: MessageState;
content: ToolCallContent[]; content: ToolCallContent[];
} }
export type IToolCallIndex = number;
export async function handleToolCalls(toolCall: ToolCall): Promise<ToolCallResult> { export async function handleToolCalls(toolCall: ToolCall): Promise<ToolCallResult> {
if (!toolCall.function) {
return {
content: [{
type: 'error',
text: 'no tool function'
}],
state: MessageState.NoToolFunction
}
}
// 反序列化 streaming 来的参数字符串 // 反序列化 streaming 来的参数字符串
const toolName = toolCall.function.name; // TODO: check as string
const argsResult = deserializeToolCallResponse(toolCall.function.arguments); const toolName = toolCall.function.name as string;
const argsResult = deserializeToolCallResponse(toolCall.function.arguments as string);
if (argsResult.error) { if (argsResult.error) {
return { return {
@ -25,7 +48,7 @@ export async function handleToolCalls(toolCall: ToolCall): Promise<ToolCallResul
const toolArgs = argsResult.value; const toolArgs = argsResult.value;
// 进行调用,根据结果返回不同的值 // 进行调用,根据结果返回不同的值
const toolResponse = await callTool(toolName, toolArgs); const toolResponse = await mcpClientAdapter.callTool(toolName, toolArgs);
return handleToolResponse(toolResponse); return handleToolResponse(toolResponse);
} }
@ -44,11 +67,8 @@ function deserializeToolCallResponse(toolArgs: string) {
} }
} }
function handleToolResponse(toolResponse: ToolCallResponse) { export function handleToolResponse(toolResponse: ToolCallResponse) {
if (typeof toolResponse === 'string') { if (typeof toolResponse === 'string') {
// 如果是 string说明是错误信息
console.log(toolResponse);
return { return {
content: [{ content: [{
@ -84,3 +104,68 @@ function parseErrorObject(error: any): string {
return error.toString(); return error.toString();
} }
} }
/**
* @description ID映射为索引
* @param toolCall
* @param callId2Index ID到索引的映射表
* @returns
*/
export function idAsIndexAdapter(toolCall: ToolCall | string, callId2Index: Map<string, number>): IToolCallIndex {
// grok 采用 id 作为 index需要将 id 映射到 zero-based 的 index
const id = typeof toolCall === 'string' ? toolCall : toolCall.id;
if (!id) {
return 0;
}
if (!callId2Index.has(id)) {
callId2Index.set(id, callId2Index.size);
}
return callId2Index.get(id)!;
}
/**
* @description
* @param toolCall
* @returns 0
*/
export function singleCallIndexAdapter(toolCall: ToolCall): IToolCallIndex {
// TODO: 等待后续支持
return 0;
}
/**
* @description
* @param toolCall
* @returns
*/
export function defaultIndexAdapter(toolCall: ToolCall): IToolCallIndex {
return toolCall.index || 0;
}
export function getToolCallIndexAdapter(llm: BasicLlmDescription, chatData: ChatCompletionCreateParamsBase) {
// 如果是 xml 模式,那么 index adapter 必须是 idAsIndexAdapter
if (chatData.enableXmlWrapper) {
const callId2Index = new Map<string, number>();
return (toolCall: ToolCall) => idAsIndexAdapter(toolCall, callId2Index);
}
if (llm.userModel.startsWith('gemini')) {
return singleCallIndexAdapter;
}
if (llm.userModel.startsWith('grok')) {
const callId2Index = new Map<string, number>();
return (toolCall: ToolCall) => idAsIndexAdapter(toolCall, callId2Index);
}
return defaultIndexAdapter;
}
export function getIdAsIndexAdapter() {
const callId2Index = new Map<string, number>();
return (toolCall: ToolCall) => idAsIndexAdapter(toolCall, callId2Index);
}

View File

@ -1,21 +1,32 @@
/* eslint-disable */ /* eslint-disable */
import { ref, type Ref } from "vue"; import { ref, type Ref } from "vue";
import { type ToolCall, type ChatStorage, getToolSchema, MessageState } from "../chat-box/chat"; import { type ToolCall, type ChatStorage, getToolSchema, MessageState, type ChatMessage, type ChatSetting, type EnableToolItem } from "../chat-box/chat";
import { useMessageBridge, MessageBridge, createMessageBridge } from "@/api/message-bridge"; import { useMessageBridge, MessageBridge, createMessageBridge } from "@/api/message-bridge";
import type { OpenAI } from 'openai'; import type { OpenAI } from 'openai';
import { llmManager, llms } from "@/views/setting/llm"; import { llmManager, llms, type BasicLlmDescription } from "@/views/setting/llm";
import { pinkLog, redLog } from "@/views/setting/util"; import { redLog } from "@/views/setting/util";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { handleToolCalls, type ToolCallResult } from "./handle-tool-calls"; import { getToolCallIndexAdapter, handleToolCalls, type IToolCallIndex, type ToolCallResult } from "./handle-tool-calls";
import { getPlatform } from "@/api/platform"; import { getPlatform } from "@/api/platform";
import { getSystemPrompt, systemPrompts } from "../chat-box/options/system-prompt"; import { getSystemPrompt } from "../chat-box/options/system-prompt";
import { mcpSetting } from "@/hook/mcp";
import { mcpClientAdapter } from "@/views/connect/core";
import type { ToolItem } from "@/hook/type";
import chalk from 'chalk';
import { getXmlWrapperPrompt, getToolCallFromXmlString, getXmlsFromString, handleXmlWrapperToolcall, toNormaliseToolcall, getXmlResultPrompt } from "./xml-wrapper";
export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk; export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string }; export interface TaskLoopChatOption {
id?: string
proxyServer?: string
enableXmlWrapper?: boolean
}
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & TaskLoopChatOption;
export interface TaskLoopOptions { export interface TaskLoopOptions {
maxEpochs?: number; maxEpochs?: number;
maxJsonParseRetry?: number; maxJsonParseRetry?: number;
adapter?: any; adapter?: any;
verbose?: 0 | 1 | 2 | 3;
} }
export interface IErrorMssage { export interface IErrorMssage {
@ -23,6 +34,8 @@ export interface IErrorMssage {
msg: string msg: string
} }
export { MessageState };
export interface IDoConversationResult { export interface IDoConversationResult {
stop: boolean; stop: boolean;
} }
@ -35,25 +48,37 @@ export class TaskLoop {
private bridge: MessageBridge; private bridge: MessageBridge;
private streamingContent: Ref<string>; private streamingContent: Ref<string>;
private streamingToolCalls: Ref<ToolCall[]>; private streamingToolCalls: Ref<ToolCall[]>;
private aborted = false;
private currentChatId = ''; private currentChatId = '';
private onError: (error: IErrorMssage) => void = (msg) => {}; private onError: (error: IErrorMssage) => void = (msg) => { };
private onChunk: (chunk: ChatCompletionChunk) => void = (chunk) => {}; private onChunk: (chunk: ChatCompletionChunk) => void = (chunk) => { };
private onDone: () => void = () => {}; private onDone: () => void = () => { };
private onToolCall: (toolCall: ToolCall) => ToolCall = toolCall => toolCall; private onToolCall: (toolCall: ToolCall) => ToolCall = toolCall => toolCall;
private onToolCalled: (toolCallResult: ToolCallResult) => ToolCallResult = toolCallResult => toolCallResult; private onToolCalled: (toolCallResult: ToolCallResult) => ToolCallResult = toolCallResult => toolCallResult;
private onEpoch: () => void = () => {}; private onEpoch: () => void = () => { };
private completionUsage: ChatCompletionChunk['usage'] | undefined; private completionUsage: ChatCompletionChunk['usage'] | undefined;
private llmConfig: any; private llmConfig?: BasicLlmDescription;
// 只会在 nodejs 环境下使用的部分变量
private nodejsStatus = {
connectionFut: new Promise<void>(resolve => resolve(void 0))
};
constructor( constructor(
private readonly taskOptions: TaskLoopOptions = { maxEpochs: 20, maxJsonParseRetry: 3, adapter: undefined }, private taskOptions: TaskLoopOptions = {
maxEpochs: 50,
maxJsonParseRetry: 3,
adapter: undefined,
verbose: 0
},
) { ) {
this.streamingContent = ref(''); this.streamingContent = ref('');
this.streamingToolCalls = ref([]); this.streamingToolCalls = ref([]);
// 根据当前环境决定是否要开启 messageBridge // 根据当前环境决定是否要开启 messageBridge
const platform = getPlatform(); const platform = getPlatform();
if (platform === 'nodejs') { if (platform === 'nodejs') {
const adapter = taskOptions.adapter; const adapter = taskOptions.adapter;
@ -61,31 +86,74 @@ export class TaskLoop {
throw new Error('adapter is required'); throw new Error('adapter is required');
} }
// 根据 adapter 创建 nodejs 下特殊的、基于 event 的 message bridge (不占用任何端口)
createMessageBridge(adapter.emitter); createMessageBridge(adapter.emitter);
// 用于进行连接同步
this.nodejsStatus.connectionFut = mcpClientAdapter.launch();
} }
// web 环境下 bridge 会自动加载完成 // web 环境下 bridge 会自动加载完成
this.bridge = useMessageBridge(); this.bridge = useMessageBridge();
// 注册 HMR
mcpClientAdapter.addConnectRefreshListener();
} }
private handleChunkDeltaContent(chunk: ChatCompletionChunk) { public async waitConnection() {
await this.nodejsStatus.connectionFut;
}
public setTaskLoopOptions(taskOptions: TaskLoopOptions) {
const {
maxEpochs = 50,
maxJsonParseRetry = 3,
verbose = 1,
} = taskOptions;
this.taskOptions = {
maxEpochs,
maxJsonParseRetry,
verbose,
...this.taskOptions
};
}
/**
* @description streaming content
* @param chunk
* @param chatData
*/
private handleChunkDeltaContent(chunk: ChatCompletionChunk, chatData: ChatCompletionCreateParamsBase) {
const content = chunk.choices[0]?.delta?.content || ''; const content = chunk.choices[0]?.delta?.content || '';
if (content) { if (content) {
this.streamingContent.value += content; this.streamingContent.value += content;
} }
} }
private handleChunkDeltaToolCalls(chunk: ChatCompletionChunk) { /**
* @description streaming chunk tool_calls
* @param chunk
* @param chatData
* @param toolcallIndexAdapter
*/
private handleChunkDeltaToolCalls(chunk: ChatCompletionChunk, chatData: ChatCompletionCreateParamsBase, toolcallIndexAdapter: (toolCall: ToolCall) => IToolCallIndex) {
const toolCall = chunk.choices[0]?.delta?.tool_calls?.[0]; const toolCall = chunk.choices[0]?.delta?.tool_calls?.[0];
if (toolCall) { if (toolCall) {
const currentCall = this.streamingToolCalls.value[toolCall.index]; if (toolCall.index === undefined || toolCall.index === null) {
console.warn('tool_call.index is undefined or null');
}
const index = toolcallIndexAdapter(toolCall);
const currentCall = this.streamingToolCalls.value[index];
if (currentCall === undefined) { if (currentCall === undefined) {
// 新的工具调用开始 // 新的工具调用开始
this.streamingToolCalls.value[toolCall.index] = { this.streamingToolCalls.value[index] = {
id: toolCall.id, id: toolCall.id,
index: toolCall.index, index,
type: 'function', type: 'function',
function: { function: {
name: toolCall.function?.name || '', name: toolCall.function?.name || '',
@ -99,10 +167,10 @@ export class TaskLoop {
currentCall.id = toolCall.id; currentCall.id = toolCall.id;
} }
if (toolCall.function?.name) { if (toolCall.function?.name) {
currentCall.function.name = toolCall.function.name; currentCall.function!.name = toolCall.function.name;
} }
if (toolCall.function?.arguments) { if (toolCall.function?.arguments) {
currentCall.function.arguments += toolCall.function.arguments; currentCall.function!.arguments += toolCall.function.arguments;
} }
} }
} }
@ -117,7 +185,7 @@ export class TaskLoop {
} }
} }
private doConversation(chatData: ChatCompletionCreateParamsBase) { private doConversation(chatData: ChatCompletionCreateParamsBase, toolcallIndexAdapter: (toolCall: ToolCall) => IToolCallIndex) {
return new Promise<IDoConversationResult>((resolve, reject) => { return new Promise<IDoConversationResult>((resolve, reject) => {
const chunkHandler = this.bridge.addCommandListener('llm/chat/completions/chunk', data => { const chunkHandler = this.bridge.addCommandListener('llm/chat/completions/chunk', data => {
@ -125,15 +193,22 @@ export class TaskLoop {
const { chunk } = data.msg as { chunk: ChatCompletionChunk }; const { chunk } = data.msg as { chunk: ChatCompletionChunk };
// 处理增量的 content 和 tool_calls // 处理增量的 content 和 tool_calls
this.handleChunkDeltaContent(chunk); if (chatData.enableXmlWrapper) {
this.handleChunkDeltaToolCalls(chunk); this.handleChunkDeltaContent(chunk, chatData);
this.handleChunkUsage(chunk); // no tool call in enableXmlWrapper
this.handleChunkUsage(chunk);
} else {
this.handleChunkDeltaContent(chunk, chatData);
this.handleChunkDeltaToolCalls(chunk, chatData, toolcallIndexAdapter);
this.handleChunkUsage(chunk);
}
this.onChunk(chunk); this.consumeChunks(chunk);
}, { once: false }); }, { once: false });
const doneHandler = this.bridge.addCommandListener('llm/chat/completions/done', data => { const doneHandler = this.bridge.addCommandListener('llm/chat/completions/done', data => {
this.onDone(); this.consumeDones();
chunkHandler(); chunkHandler();
errorHandler(); errorHandler();
@ -143,7 +218,7 @@ export class TaskLoop {
}, { once: true }); }, { once: true });
const errorHandler = this.bridge.addCommandListener('llm/chat/completions/error', data => { const errorHandler = this.bridge.addCommandListener('llm/chat/completions/error', data => {
this.onError({ this.consumeErrors({
state: MessageState.ReceiveChunkError, state: MessageState.ReceiveChunkError,
msg: data.msg || '请求模型服务时发生错误' msg: data.msg || '请求模型服务时发生错误'
}); });
@ -157,8 +232,6 @@ export class TaskLoop {
}, { once: true }); }, { once: true });
console.log(chatData);
this.bridge.postMessage({ this.bridge.postMessage({
command: 'llm/chat/completions', command: 'llm/chat/completions',
data: JSON.parse(JSON.stringify(chatData)), data: JSON.parse(JSON.stringify(chatData)),
@ -166,6 +239,10 @@ export class TaskLoop {
}); });
} }
public setProxyServer(proxyServer: string) {
mcpSetting.proxyServer = proxyServer;
}
public makeChatData(tabStorage: ChatStorage): ChatCompletionCreateParamsBase | undefined { public makeChatData(tabStorage: ChatStorage): ChatCompletionCreateParamsBase | undefined {
const baseURL = this.getLlmConfig().baseUrl; const baseURL = this.getLlmConfig().baseUrl;
const apiKey = this.getLlmConfig().userToken || ''; const apiKey = this.getLlmConfig().userToken || '';
@ -181,21 +258,35 @@ export class TaskLoop {
const model = this.getLlmConfig().userModel; const model = this.getLlmConfig().userModel;
const temperature = tabStorage.settings.temperature; const temperature = tabStorage.settings.temperature;
const tools = getToolSchema(tabStorage.settings.enableTools); const parallelToolCalls = tabStorage.settings.parallelToolCalls;
const proxyServer = mcpSetting.proxyServer || '';
// 如果是 xml 模式,则 tools 为空
const enableXmlWrapper = tabStorage.settings.enableXmlWrapper;
const tools = enableXmlWrapper ? []: getToolSchema(tabStorage.settings.enableTools);
const userMessages = []; const userMessages = [];
// 尝试获取 system prompt在 api 模式下systemPrompt 就是目标提词 // 尝试获取 system prompt在 api 模式下systemPrompt 就是目标提词
// 但是在 UI 模式下systemPrompt 只是一个 index需要从后端数据库中获取真实 prompt // 但是在 UI 模式下systemPrompt 只是一个 index需要从后端数据库中获取真实 prompt
if (tabStorage.settings.systemPrompt) {
const prompt = getSystemPrompt(tabStorage.settings.systemPrompt) || tabStorage.settings.systemPrompt;
userMessages.push({ let prompt = '';
role: 'system',
content: prompt // 如果存在系统提示词,则从数据库中获取对应的数据
}); if (tabStorage.settings.systemPrompt) {
prompt += getSystemPrompt(tabStorage.settings.systemPrompt) || tabStorage.settings.systemPrompt;
} }
// 如果是 xml 模式,则在开头注入 xml
if (enableXmlWrapper) {
prompt += getXmlWrapperPrompt(tabStorage.settings.enableTools, tabStorage);
}
userMessages.push({
role: 'system',
content: prompt
});
// 如果超出了 tabStorage.settings.contextLength, 则删除最早的消息 // 如果超出了 tabStorage.settings.contextLength, 则删除最早的消息
const loadMessages = tabStorage.messages.slice(- tabStorage.settings.contextLength); const loadMessages = tabStorage.messages.slice(- tabStorage.settings.contextLength);
userMessages.push(...loadMessages); userMessages.push(...loadMessages);
@ -210,7 +301,10 @@ export class TaskLoop {
model, model,
temperature, temperature,
tools, tools,
parallelToolCalls,
messages: userMessages, messages: userMessages,
proxyServer,
enableXmlWrapper,
} as ChatCompletionCreateParamsBase; } as ChatCompletionCreateParamsBase;
return chatData; return chatData;
@ -225,6 +319,7 @@ export class TaskLoop {
}); });
this.streamingContent.value = ''; this.streamingContent.value = '';
this.streamingToolCalls.value = []; this.streamingToolCalls.value = [];
this.aborted = true;
} }
/** /**
@ -271,6 +366,102 @@ export class TaskLoop {
this.onToolCalled = handler; this.onToolCalled = handler;
} }
private consumeErrors(error: IErrorMssage) {
const { verbose = 0 } = this.taskOptions;
if (verbose > 0) {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.red('error happen in task loop '),
chalk.red(error.msg)
);
}
return this.onError(error);
}
private consumeChunks(chunk: ChatCompletionChunk) {
const { verbose = 0 } = this.taskOptions;
if (verbose > 1) {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.blue('receive chunk')
);
} else if (verbose > 2) {
const delta = chunk.choices[0]?.delta;
if (delta) {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.blue('receive chunk'),
chalk.bold(JSON.stringify(delta, null, 2))
);
} else {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.blue('receive chunk'),
chalk.blue('delta is empty')
);
}
}
return this.onChunk(chunk);
}
private consumeToolCalls(toolCall: ToolCall) {
const { verbose = 0 } = this.taskOptions;
if (verbose > 0) {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.blueBright('🔧 using tool'),
chalk.blueBright(toolCall.function!.name)
);
}
return this.onToolCall(toolCall);
}
private consumeToolCalleds(result: ToolCallResult) {
const { verbose = 0 } = this.taskOptions;
if (verbose > 0) {
if (result.state === 'success') {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.green('✓ use tools'),
chalk.green(result.state)
);
} else {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.red('× use tools'),
chalk.red(result.content.map(item => item.text).join(', '))
);
}
}
return this.onToolCalled(result);
}
private consumeEpochs() {
const { verbose = 0 } = this.taskOptions;
if (verbose > 1) {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.blue('task loop enters a new epoch')
);
}
return this.onEpoch();
}
private consumeDones() {
const { verbose = 0 } = this.taskOptions;
if (verbose > 1) {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.green('task loop finish a epoch')
);
}
return this.onDone();
}
public setMaxEpochs(maxEpochs: number) { public setMaxEpochs(maxEpochs: number) {
this.taskOptions.maxEpochs = maxEpochs; this.taskOptions.maxEpochs = maxEpochs;
} }
@ -302,14 +493,43 @@ export class TaskLoop {
return llms[llmManager.currentModelIndex]; return llms[llmManager.currentModelIndex];
} }
public async connectToService() { public async listTools() {
const platform = getPlatform();
if (platform === 'nodejs') {
// 等待连接完成
await this.nodejsStatus.connectionFut;
}
const allTools = [] as ToolItem[];
for (const client of mcpClientAdapter.clients) {
if (!client.connected) {
continue;
}
const tools = await client.getTools();
allTools.push(...Array.from(tools.values()).map(
item => ({
...item,
enabled: true
})
));
}
return allTools;
} }
/** /**
* @description DOM * @description DOM
*/ */
public async start(tabStorage: ChatStorage, userMessage: string) { public async start(tabStorage: ChatStorage, userMessage: string) {
const platform = getPlatform();
if (platform === 'nodejs') {
// 等待连接完成
await this.nodejsStatus.connectionFut;
}
const enableXmlWrapper = tabStorage.settings.enableXmlWrapper;
// 添加目前的消息 // 添加目前的消息
tabStorage.messages.push({ tabStorage.messages.push({
role: 'user', role: 'user',
@ -317,16 +537,21 @@ export class TaskLoop {
extraInfo: { extraInfo: {
created: Date.now(), created: Date.now(),
state: MessageState.Success, state: MessageState.Success,
serverName: this.getLlmConfig().id || 'unknown' serverName: this.getLlmConfig().id || 'unknown',
enableXmlWrapper
} }
}); });
let jsonParseErrorRetryCount = 0; let jsonParseErrorRetryCount = 0;
const maxEpochs = this.taskOptions.maxEpochs || 20; const {
maxEpochs = 50,
verbose = 0
} = this.taskOptions || {};
this.aborted = false;
for (let i = 0; i < maxEpochs; ++ i) { for (let i = 0; i < maxEpochs; ++i) {
this.onEpoch(); this.consumeEpochs();
// 初始累计清空 // 初始累计清空
this.streamingContent.value = ''; this.streamingContent.value = '';
@ -337,17 +562,22 @@ export class TaskLoop {
const chatData = this.makeChatData(tabStorage); const chatData = this.makeChatData(tabStorage);
if (!chatData) { if (!chatData) {
this.onDone(); this.consumeDones();
break; break;
} }
this.currentChatId = chatData.id!; this.currentChatId = chatData.id!;
const llm = this.getLlmConfig();
const toolcallIndexAdapter = getToolCallIndexAdapter(llm, chatData);
// 发送请求 // 发送请求
const doConverationResult = await this.doConversation(chatData); const doConverationResult = await this.doConversation(chatData, toolcallIndexAdapter);
console.log('[doConverationResult] Response'); // 如果在调用过程中出发了 abort则直接中断
console.log(doConverationResult); if (this.aborted) {
this.aborted = false;
break;
}
// 如果存在需要调度的工具 // 如果存在需要调度的工具
if (this.streamingToolCalls.value.length > 0) { if (this.streamingToolCalls.value.length > 0) {
@ -359,22 +589,43 @@ export class TaskLoop {
extraInfo: { extraInfo: {
created: Date.now(), created: Date.now(),
state: MessageState.Success, state: MessageState.Success,
serverName: this.getLlmConfig().id || 'unknown' serverName: this.getLlmConfig().id || 'unknown',
enableXmlWrapper
} }
}); });
pinkLog('调用工具数量:' + this.streamingToolCalls.value.length); if (verbose > 0) {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.yellow('🤖 Agent wants to use these tools'),
chalk.yellow(this.streamingToolCalls.value.map(tool => tool.function!.name || '').join(', '))
);
}
for (let toolCall of this.streamingToolCalls.value || []) { for (let toolCall of this.streamingToolCalls.value || []) {
toolCall = this.onToolCall(toolCall); // ready to call tools
toolCall = this.consumeToolCalls(toolCall);
if (this.aborted) {
this.aborted = false;
break;
}
let toolCallResult = await handleToolCalls(toolCall); let toolCallResult = await handleToolCalls(toolCall);
toolCallResult = this.onToolCalled(toolCallResult);
if (this.aborted) {
this.aborted = false;
break;
}
// hook : finish call tools
toolCallResult = this.consumeToolCalleds(toolCallResult);
if (toolCallResult.state === MessageState.ParseJsonError) { if (toolCallResult.state === MessageState.ParseJsonError) {
// 如果是因为解析 JSON 错误,则重新开始 // 如果是因为解析 JSON 错误,则重新开始
tabStorage.messages.pop(); tabStorage.messages.pop();
jsonParseErrorRetryCount ++; jsonParseErrorRetryCount++;
redLog('解析 JSON 错误 ' + toolCall?.function?.arguments); redLog('解析 JSON 错误 ' + toolCall?.function?.arguments);
@ -387,7 +638,8 @@ export class TaskLoop {
created: Date.now(), created: Date.now(),
state: toolCallResult.state, state: toolCallResult.state,
serverName: this.getLlmConfig().id || 'unknown', serverName: this.getLlmConfig().id || 'unknown',
usage: undefined usage: undefined,
enableXmlWrapper
} }
}); });
break; break;
@ -395,33 +647,40 @@ export class TaskLoop {
} else if (toolCallResult.state === MessageState.Success) { } else if (toolCallResult.state === MessageState.Success) {
tabStorage.messages.push({ tabStorage.messages.push({
role: 'tool', role: 'tool',
index: toolCall.index || 0, index: toolcallIndexAdapter(toolCall),
tool_call_id: toolCall.id || toolCall.function.name, tool_call_id: toolCall.id || '',
content: toolCallResult.content, content: toolCallResult.content,
extraInfo: { extraInfo: {
created: Date.now(), created: Date.now(),
state: toolCallResult.state, state: toolCallResult.state,
serverName: this.getLlmConfig().id || 'unknown', serverName: this.getLlmConfig().id || 'unknown',
usage: this.completionUsage usage: this.completionUsage,
enableXmlWrapper
} }
}); });
} else if (toolCallResult.state === MessageState.ToolCall) { } else if (toolCallResult.state === MessageState.ToolCall) {
tabStorage.messages.push({ tabStorage.messages.push({
role: 'tool', role: 'tool',
index: toolCall.index || 0, index: toolcallIndexAdapter(toolCall),
tool_call_id: toolCall.id || toolCall.function.name, tool_call_id: toolCall.id || toolCall.function!.name,
content: toolCallResult.content, content: toolCallResult.content,
extraInfo: { extraInfo: {
created: Date.now(), created: Date.now(),
state: toolCallResult.state, state: toolCallResult.state,
serverName: this.getLlmConfig().id || 'unknown', serverName: this.getLlmConfig().id || 'unknown',
usage: this.completionUsage usage: this.completionUsage,
enableXmlWrapper
} }
}); });
} }
} }
if (this.aborted) {
this.aborted = false;
break;
}
} else if (this.streamingContent.value) { } else if (this.streamingContent.value) {
tabStorage.messages.push({ tabStorage.messages.push({
role: 'assistant', role: 'assistant',
@ -430,10 +689,96 @@ export class TaskLoop {
created: Date.now(), created: Date.now(),
state: MessageState.Success, state: MessageState.Success,
serverName: this.getLlmConfig().id || 'unknown', serverName: this.getLlmConfig().id || 'unknown',
usage: this.completionUsage usage: this.completionUsage,
enableXmlWrapper
} }
}); });
break;
// 如果 xml 模型,需要检查内部是否含有有效的 xml 进行调用
if (tabStorage.settings.enableXmlWrapper) {
const xmls = getXmlsFromString(this.streamingContent.value);
if (xmls.length === 0) {
// 没有 xml 了,说明对话结束
break;
}
// 使用 user 作为身份来承载 xml 调用的结果
// 并且在 extra 内存储结构化信息
const fakeUserMessage = {
role: 'user',
content: '',
extraInfo: {
created: Date.now(),
state: MessageState.Success,
serverName: this.getLlmConfig().id || 'unknown',
usage: this.completionUsage,
enableXmlWrapper,
}
} as ChatMessage;
// 有 xml 了,需要检查 xml 内部是否有有效的 xml 进行调用
for (const xml of xmls) {
const toolcall = await getToolCallFromXmlString(xml);
if (!toolcall) {
continue;
}
// toolcall 事件
// 此处使用的是 xml 使用的 toolcall为了保持一致性需要转换成 openai 标准下的 toolcall
const normaliseToolcall = toNormaliseToolcall(toolcall, toolcallIndexAdapter);
this.consumeToolCalls(normaliseToolcall);
// 调用 XML 调用,其实可以考虑后续把这个循环改成 Promise.race
const toolCallResult = await handleXmlWrapperToolcall(toolcall);
// toolcalled 事件
// 因为是交付给后续进行统一消费的,所以此处的输出满足 openai 接口规范
this.consumeToolCalleds(toolCallResult);
// XML 模式下只存在 assistant 和 user 这两个角色,因此,以 user 为身份来存储
if (toolCallResult.state === MessageState.InvalidXml) {
// 如果是因为解析 XML 错误,则重新开始
tabStorage.messages.pop();
jsonParseErrorRetryCount ++;
redLog('解析 XML 错误 ' + normaliseToolcall?.function?.arguments);
// 如果因为 XML 错误而失败太多,就只能中断了
if (jsonParseErrorRetryCount >= (this.taskOptions.maxJsonParseRetry || 3)) {
const prompt = getXmlResultPrompt(toolcall.callId, `解析 XML 错误,无法继续调用工具 (累计错误次数 ${this.taskOptions.maxJsonParseRetry})`);
fakeUserMessage.content += prompt;
break;
}
} else if (toolCallResult.state === MessageState.Success) {
// TODO: xml 目前只支持 text 类型的回复
const toolCallResultString = toolCallResult.content
.filter(c => c.type === 'text')
.map(c => c.text)
.join('\n');
fakeUserMessage.content += getXmlResultPrompt(toolcall.callId, toolCallResultString);
} else if (toolCallResult.state === MessageState.ToolCall) {
// TODO: xml 目前只支持 text 类型的回复
const toolCallResultString = toolCallResult.content
.filter(c => c.type === 'text')
.map(c => c.text)
.join('\n');
fakeUserMessage.content += getXmlResultPrompt(toolcall.callId, toolCallResultString);
}
}
tabStorage.messages.push(fakeUserMessage);
} else {
// 普通对话直接结束
break;
}
} else { } else {
// 一些提示 // 一些提示
@ -446,4 +791,52 @@ export class TaskLoop {
} }
} }
} }
public async createStorage(settings?: Partial<ChatSetting>): Promise<ChatStorage> {
let {
enableXmlWrapper = false,
systemPrompt = '',
temperature = 0.6,
contextLength = 100,
parallelToolCalls = true,
enableWebSearch = false,
enableTools = undefined,
} = settings || {};
if (enableTools === undefined) {
// 默认缺省的情况下使用全部工具
const tools = await this.listTools();
enableTools = tools.map(tool => ({
...tool,
enabled: true
})) as EnableToolItem[];
}
const _settings = {
enableXmlWrapper,
systemPrompt,
temperature,
contextLength,
parallelToolCalls,
enableTools,
enableWebSearch
} as ChatSetting;
return {
messages: [],
settings: _settings
}
}
public async getPrompt(promptId: string, args?: Record<string, any>) {
const prompt = await mcpClientAdapter.readPromptTemplate(promptId, args);
// transform prompt to string
const promptString = prompt.messages.map(m => m.content.text).join('\n');
return promptString;
}
public async getResource(resourceUri: string) {
const resource = await mcpClientAdapter.readResource(resourceUri);
return resource;
}
} }

View File

@ -0,0 +1,294 @@
import { parseString } from 'xml2js';
import { MessageState, type ToolCall } from '../chat-box/chat';
import { mcpClientAdapter } from '@/views/connect/core';
import { handleToolResponse, type IToolCallIndex, type ToolCallResult } from './handle-tool-calls';
import type { ChatStorage, EnableToolItem } from "../chat-box/chat";
import type { ToolCallContent } from '@/hook/type';
export interface XmlToolCall {
server: string;
name: string;
callId: string;
parameters: Record<string, string>;
}
export function toolSchemaToPromptDescription(enableTools: EnableToolItem[]) {
let prompt = '';
const tools = enableTools.filter(tool => tool.enabled);
// 无参数的工具
const noParamTools = tools.filter(tool =>
!tool.inputSchema.required || tool.inputSchema.required.length === 0
);
if (noParamTools.length > 0) {
noParamTools.forEach(tool => {
prompt += `- ${tool.name}\n`;
prompt += `**Description**: ${tool.description}\n\n`;
});
}
// 有参数的工具
const paramTools = tools.filter(tool =>
tool.inputSchema.required && tool.inputSchema.required.length > 0
);
if (paramTools.length > 0) {
paramTools.forEach(tool => {
prompt += `- ${tool.name}\n`;
prompt += `**Description**: ${tool.description}\n`;
prompt += `**Parameters**:\n`;
Object.entries(tool.inputSchema.properties).forEach(([name, prop]) => {
const required = tool.inputSchema.required?.includes(name) || false;
prompt += `- \`${name}\`: ${prop.description || 'No Description'} (${prop.type}) ${required ? '(required)' : ''}\n`;
});
prompt += '\n';
});
}
return prompt;
}
export function getXmlWrapperPrompt(tools: EnableToolItem[], tabStorage: ChatStorage) {
const toolPrompt = toolSchemaToPromptDescription(tools);
const requests = [
`ALWAYS analyze what function calls would be appropriate for the task`,
`ALWAYS format your function call usage EXACTLY as specified in the schema`,
`NEVER skip required parameters in function calls`,
`NEVER invent functions that arent available to you`,
`ALWAYS wait for function call execution results before continuing`,
`After invoking a function, wait for the output in <function_results> tag and then continue with your response`,
`NEVER mock or form <function_results> on your own, it will be provided to you after the execution`,
];
if (!tabStorage.settings.parallelToolCalls) {
requests.push(`NEVER invoke multiple functions in a single response`);
}
const requestString = requests.map((text, index) => {
return `${index + 1}. ${text}`;
}).join('\n');
return `
[Start Fresh Session from here]
<SYSTEM>
You are OpenMCP Assistant with the capabilities of invoke functions and make the best use of it during your assistance, a knowledgeable assistant focused on answering questions and providing information on any topics.
In this environment you have access to a set of tools you can use to answer the user's question.
You have access to a set of functions you can use to answer the user's question. You do NOT currently have the ability to inspect files or interact with external resources, except by invoking the below functions.
Function Call Structure:
- All function calls should be wrapped in 'xml' codeblocks tags like \`\`\`xml ... \`\`\`. This is strict requirement.
- Wrap all function calls in 'function_calls' tags
- Each function call uses 'invoke' tags with a 'name' attribute
- Parameters use 'parameter' tags with 'name' attributes
- Parameter Formatting:
- String/scalar parameters: written directly as values
- Lists/objects: must use proper JSON format
- Required parameters must always be included
- Optional parameters should only be included when needed
- If there is xml inside the parameter value, do not use CDATA for wrapping it, just give the xml directly
The instructions regarding 'invoke' specify that:
- When invoking functions, use the 'invoke' tag with a 'name' attribute specifying the function name.
- The invoke tag must be nested within an 'function_calls' block.
- Parameters for the function should be included as 'parameter' tags within the invoke tag, each with a 'name' attribute.
- Include all required parameters for each function call, while optional parameters should only be included when necessary.
- String and scalar parameters should be specified directly as values, while lists and objects should use proper JSON format.
- Do not refer to function/tool names when speaking directly to users - focus on what I'm doing rather than the tool I'm using.
- When invoking a function, ensure all necessary context is provided for the function to execute properly.
- Each 'invoke' tag should represent a single, complete function call with all its relevant parameters.
- DO not generate any <function_calls> tag in your thinking/resoning process, because those will be interpreted as a function call and executed. just formulate the correct parameters for the function call.
The instructions regarding 'call_id="$CALL_ID">
- It is a unique identifier for the function call.
- It is a number that is incremented by 1 for each new function call, starting from 1.
You can invoke one or more functions by writing a "<function_calls>" block like the following as part of your reply to the user, MAKE SURE TO INVOKE ONLY ONE FUNCTION AT A TIME, meaning only one '<function_calls>' tag in your output :
<Example>
\`\`\`xml
<function_calls>
<invoke name="$FUNCTION_NAME" call_id="$CALL_ID">
<parameter name="$PARAMETER_NAME_1">$PARAMETER_VALUE</parameter>
<parameter name="$PARAMETER_NAME_2">$PARAMETER_VALUE</parameter>
...
</invoke>
</function_calls>
\`\`\`
</Example>
String and scalar parameters should be specified as is, while lists and objects should use JSON format. Note that spaces for string values are not stripped. The output is not expected to be valid XML and is parsed with regular expressions.
When a user makes a request:
${requestString}
Answer the user's request using the relevant tool(s), if they are available. Check that all the required parameters for each tool call are provided or can reasonably be inferred from context. IF there are no relevant tools or there are missing values for required parameters, ask the user to supply these values; otherwise proceed with the tool calls. If the user provides a specific value for a parameter (for example provided in quotes), make sure to use that value EXACTLY. DO NOT make up values for or ask about optional parameters. Carefully analyze descriptive terms in the request as they may indicate required parameter values that should be included even if not explicitly quoted.
<Output Format>
<Start HERE>
## Thoughts
- User Query Elaboration:
- Thoughts:
- Observations:
- Solutions:
- Function to be used:
- call_id: $CALL_ID + 1 = $CALL_ID
\`\`\`xml
<function_calls>
<invoke name="$FUNCTION_NAME" call_id="$CALL_ID">
<parameter name="$PARAMETER_NAME_1">$PARAMETER_VALUE</parameter>
<parameter name="$PARAMETER_NAME_2">$PARAMETER_VALUE</parameter>
...
</invoke>
</function_calls>
\`\`\`
<End HERE>
</Output Format>
Do not use <Start HERE> and <End HERE> in your output, that is just output format reference to where to start and end your output.
## AVAILABLE TOOLS FOR SUPERASSISTANT
${toolPrompt}
- list_servers
**Description**: List all connected MCP servers and their capabilities
- get_server_info
**Description**: Get detailed information about a specific server
**Parameters**:
- \`serverName\`: Name of the server to get info for (string) (required)
</SYSTEM>
User Interaction Starts here:
`.trim();
}
export function getXmlResultPrompt(callId: string, result: string) {
return `
\`\`\`xml
<function_results>
<result call_id="${callId}">
${result}
</result>
</function_results>
\`\`\`
`.trim() + '\n\n';
}
export function getXmlsFromString(content: string) {
const matches = content.matchAll(/```xml\n([\s\S]*?)\n```/g);
return Array.from(matches).map(match => match[1].trim());
}
export async function getToolCallFromXmlString(xmlString: string): Promise<XmlToolCall | null> {
try {
const result = await new Promise<any>((resolve, reject) => {
parseString(xmlString, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
if (!result?.function_calls?.invoke) {
return null;
}
const invoke = result.function_calls.invoke[0].$;
const parameters: Record<string, any> = {};
if (result.function_calls.invoke[0].parameter) {
result.function_calls.invoke[0].parameter.forEach((param: any) => {
const name = param.$.name as string;
parameters[name] = param._;
});
}
// name 可能是 neo4j-mcp.executeReadOnlyCypherQuery
return {
server: '',
name: invoke.name,
callId: invoke.call_id,
parameters
};
} catch (error) {
console.error('Failed to parse function calls:', error);
return null;
}
}
export async function getToolResultFromXmlString(xmlString: string) {
try {
const result = await new Promise<any>((resolve, reject) => {
parseString(xmlString, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
if (!result?.function_results?.result) {
return null;
}
const resultData = result.function_results.result[0];
const callId = resultData.$.call_id;
// 提取所有评论文本
const toolcallContent = [] as ToolCallContent[];
const content = resultData._;
toolcallContent.push({
type: 'text',
text: content
});
return {
callId,
toolcallContent
};
} catch (error) {
console.error('Failed to parse function results:', error);
return null;
}
}
export function toNormaliseToolcall(xmlToolcall: XmlToolCall, toolcallIndexAdapter: (toolCall: ToolCall) => IToolCallIndex): ToolCall {
const toolcall = {
id: xmlToolcall.callId,
index: -1,
type: 'function',
function: {
name: xmlToolcall.name,
arguments: JSON.stringify(xmlToolcall.parameters)
}
} as ToolCall;
toolcall.index = toolcallIndexAdapter(toolcall);
return toolcall;
}
export async function handleXmlWrapperToolcall(toolcall: XmlToolCall): Promise<ToolCallResult> {
if (!toolcall) {
return {
content: [{
type: 'error',
text: 'invalid xml'
}],
state: MessageState.InvalidXml
}
}
const toolResponse = await mcpClientAdapter.callTool(toolcall.name, toolcall.parameters);
return handleToolResponse(toolResponse);
}

View File

@ -1,10 +1,10 @@
<template> <template>
<div class="chat-container" :ref="el => chatContainerRef = el"> <div class="chat-container" :ref="el => chatContainerRef = el">
<el-scrollbar ref="scrollbarRef" :height="'90%'" @scroll="handleScroll" v-if="renderMessages.length > 0 || isLoading"> <el-scrollbar ref="scrollbarRef" :height="'90%'" @scroll="handleScroll"
v-if="renderMessages.length > 0 || isLoading">
<div class="message-list" :ref="el => messageListRef = el"> <div class="message-list" :ref="el => messageListRef = el">
<div v-for="(message, index) in renderMessages" :key="index" <div v-for="(message, index) in renderMessages" :key="index"
:class="['message-item', message.role.split('/')[0], message.role.split('/')[1]]" :class="['message-item', message.role.split('/')[0], message.role.split('/')[1]]">
>
<div class="message-avatar" v-if="message.role === 'assistant/content'"> <div class="message-avatar" v-if="message.role === 'assistant/content'">
<span class="iconfont icon-robot"></span> <span class="iconfont icon-robot"></span>
</div> </div>
@ -23,10 +23,8 @@
<!-- 助手调用的工具部分 --> <!-- 助手调用的工具部分 -->
<div class="message-content" v-else-if="message.role === 'assistant/tool_calls'"> <div class="message-content" v-else-if="message.role === 'assistant/tool_calls'">
<Message.Toolcall <Message.Toolcall :message="message" :tab-id="props.tabId"
:message="message" :tab-id="props.tabId" @update:tool-result="(value, toolIndex, index) => message.toolResults[toolIndex][index] = value" />
@update:tool-result="(value, toolIndex, index) => message.toolResults[toolIndex][index] = value"
/>
</div> </div>
</div> </div>
@ -47,23 +45,22 @@
</div> </div>
</div> </div>
<ChatBox <ChatBox :ref="el => footerRef = el" :tab-id="props.tabId" />
:ref="el => footerRef = el"
:tab-id="props.tabId"
/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, defineComponent, defineProps, onUnmounted, computed, nextTick, watch, provide } from 'vue'; import { ref, defineComponent, defineProps, onUnmounted, computed, nextTick, watch, provide, watchEffect } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { ElMessage, type ScrollbarInstance } from 'element-plus'; import { type ScrollbarInstance } from 'element-plus';
import { tabs } from '../panel'; import { tabs } from '../panel';
import type { ChatMessage, ChatStorage, IRenderMessage, ToolCall } from './chat-box/chat'; import type { ChatMessage, ChatStorage, IRenderMessage, ToolCall } from './chat-box/chat';
import { MessageState } from './chat-box/chat'; import { MessageState } from './chat-box/chat';
import * as Message from './message'; import * as Message from './message';
import ChatBox from './chat-box/index.vue'; import ChatBox from './chat-box/index.vue';
import { getToolCallFromXmlString, getToolResultFromXmlString, getXmlsFromString, toNormaliseToolcall } from './core/xml-wrapper';
import { getIdAsIndexAdapter } from './core/handle-tool-calls';
defineComponent({ name: 'chat' }); defineComponent({ name: 'chat' });
@ -85,18 +82,71 @@ if (!tabStorage.messages) {
tabStorage.messages = [] as ChatMessage[]; tabStorage.messages = [] as ChatMessage[];
} }
const renderMessages = computed(() => { function getXmlToolCalls(message: ChatMessage) {
const messages: IRenderMessage[] = []; if (message.role !== 'assistant' && message.role !== 'user') {
return [];
}
const enableXmlTools = message.extraInfo?.enableXmlWrapper ?? false;
if (!enableXmlTools) {
return [];
}
const xmls = getXmlsFromString(message.content);
return xmls || [];
}
const renderMessages = ref<IRenderMessage[]>([]);
watchEffect(async () => {
renderMessages.value = [];
for (const message of tabStorage.messages) { for (const message of tabStorage.messages) {
const indexAdapter = getIdAsIndexAdapter();
const xmls = getXmlToolCalls(message);
if (message.role === 'user') { if (message.role === 'user') {
messages.push({ if (xmls.length > 0 && message.extraInfo.enableXmlWrapper) {
role: 'user', // xml xml xml
content: message.content, // assistant/tool_calls
extraInfo: message.extraInfo const lastAssistantMessage = renderMessages.value[renderMessages.value.length - 1];
}); if (lastAssistantMessage.role === 'assistant/tool_calls') {
const toolCallResultXmls = getXmlsFromString(message.content);
for (const xml of toolCallResultXmls) {
const toolResult = await getToolResultFromXmlString(xml);
if (toolResult) {
const index = indexAdapter(toolResult.callId);
lastAssistantMessage.toolResults[index] = toolResult.toolcallContent;
if (lastAssistantMessage.extraInfo.state === MessageState.Unknown) {
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
} else if (lastAssistantMessage.extraInfo.state === MessageState.Success
|| message.extraInfo.state !== MessageState.Success
) {
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
}
lastAssistantMessage.extraInfo.usage = lastAssistantMessage.extraInfo.usage || message.extraInfo.usage;
}
}
}
} else {
renderMessages.value.push({
role: 'user',
content: message.content,
extraInfo: message.extraInfo
});
}
} else if (message.role === 'assistant') { } else if (message.role === 'assistant') {
if (message.tool_calls) { if (message.tool_calls) {
messages.push({ renderMessages.value.push({
role: 'assistant/tool_calls', role: 'assistant/tool_calls',
content: message.content, content: message.content,
toolResults: Array(message.tool_calls.length).fill([]), toolResults: Array(message.tool_calls.length).fill([]),
@ -108,16 +158,43 @@ const renderMessages = computed(() => {
} }
}); });
} else { } else {
messages.push({ if (xmls.length > 0 && message.extraInfo.enableXmlWrapper) {
role: 'assistant/content', // xml xml xml
content: message.content, const toolCalls = [];
extraInfo: message.extraInfo for (const xml of xmls) {
}); const xmlToolCall = await getToolCallFromXmlString(xml);
if (xmlToolCall) {
toolCalls.push(
toNormaliseToolcall(xmlToolCall, indexAdapter)
);
}
}
const renderAssistantMessage = message.content.replace(/```xml[\s\S]*?```/g, '');
renderMessages.value.push({
role: 'assistant/tool_calls',
content: renderAssistantMessage,
toolResults: Array(toolCalls.length).fill([]),
tool_calls: toolCalls,
showJson: ref(false),
extraInfo: {
...message.extraInfo,
state: MessageState.Unknown
}
});
} else {
renderMessages.value.push({
role: 'assistant/content',
content: message.content,
extraInfo: message.extraInfo
});
}
} }
} else if (message.role === 'tool') { } else if (message.role === 'tool') {
// assistant // assistant
const lastAssistantMessage = messages[messages.length - 1]; const lastAssistantMessage = renderMessages.value[renderMessages.value.length - 1];
if (lastAssistantMessage.role === 'assistant/tool_calls') { if (lastAssistantMessage.role === 'assistant/tool_calls') {
lastAssistantMessage.toolResults[message.index] = message.content; lastAssistantMessage.toolResults[message.index] = message.content;
@ -133,10 +210,9 @@ const renderMessages = computed(() => {
} }
} }
} }
return messages;
}); });
const isLoading = ref(false); const isLoading = ref(false);
const streamingContent = ref(''); const streamingContent = ref('');
@ -232,14 +308,14 @@ watch(streamingToolCalls, () => {
padding-top: 70px; padding-top: 70px;
} }
.chat-openmcp-icon > div { .chat-openmcp-icon>div {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: left; align-items: left;
font-size: 28px; font-size: 28px;
} }
.chat-openmcp-icon > div > span { .chat-openmcp-icon>div>span {
margin-bottom: 23px; margin-bottom: 23px;
} }
@ -285,7 +361,7 @@ watch(streamingToolCalls, () => {
width: 100%; width: 100%;
} }
.user .message-text > span { .user .message-text>span {
border-radius: .9em; border-radius: .9em;
background-color: var(--main-light-color); background-color: var(--main-light-color);
padding: 10px 15px; padding: 10px 15px;
@ -340,9 +416,12 @@ watch(streamingToolCalls, () => {
@keyframes spin { @keyframes spin {
0% { transform: rotate(0deg); } 0% {
100% { transform: rotate(360deg); } transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
} }
</style> </style>

View File

@ -66,7 +66,7 @@ export default function highlight(option: HighlightOption = {}) {
return; return;
} }
window.navigator.clipboard.writeText(code).then(() => { navigator.clipboard.writeText(code).then(() => {
const originalText = button.textContent; const originalText = button.textContent;
button.textContent = '已复制'; button.textContent = '已复制';
setTimeout(() => { setTimeout(() => {

View File

@ -110,7 +110,7 @@ const showFullImage = () => {
const img = new Image(); const img = new Image();
img.src = thumbnail.value; img.src = thumbnail.value;
img.onload = () => { img.onload = () => {
const overlay = document.createElement('div'); const overlay = document?.createElement('div');
overlay.style.position = 'fixed'; overlay.style.position = 'fixed';
overlay.style.top = '0'; overlay.style.top = '0';
overlay.style.left = '0'; overlay.style.left = '0';
@ -121,9 +121,9 @@ const showFullImage = () => {
overlay.style.display = 'flex'; overlay.style.display = 'flex';
overlay.style.justifyContent = 'center'; overlay.style.justifyContent = 'center';
overlay.style.alignItems = 'center'; overlay.style.alignItems = 'center';
overlay.onclick = () => document.body.removeChild(overlay); overlay.onclick = () => document?.body.removeChild(overlay);
const imgContainer = document.createElement('div'); const imgContainer = document?.createElement('div');
imgContainer.style.maxWidth = '90vw'; imgContainer.style.maxWidth = '90vw';
imgContainer.style.maxHeight = '90vh'; imgContainer.style.maxHeight = '90vh';
imgContainer.style.overflow = 'auto'; imgContainer.style.overflow = 'auto';
@ -137,7 +137,7 @@ const showFullImage = () => {
imgContainer.appendChild(fullImg); imgContainer.appendChild(fullImg);
overlay.appendChild(imgContainer); overlay.appendChild(imgContainer);
document.body.appendChild(overlay); document?.body.appendChild(overlay);
}; };
}; };

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="message-role"> <div class="message-role">
<span class="message-reminder" v-if="callingTools"> <span class="message-reminder" v-if="callingTools">
Agent 正在使用工具 Agent {{ t('using-tool') }}
<span class="tool-loading iconfont icon-double-loading"> <span class="tool-loading iconfont icon-double-loading">
</span> </span>
</span> </span>
@ -20,7 +20,7 @@
<span class="tool-name"> <span class="tool-name">
<span class="iconfont icon-tool"></span> <span class="iconfont icon-tool"></span>
{{ props.message.tool_calls[0].function.name }} {{ props.message.tool_calls[0].function!.name }}
</span> </span>
<el-button size="small" @click="createTest(props.message.tool_calls[0])"> <el-button size="small" @click="createTest(props.message.tool_calls[0])">
<span class="iconfont icon-send"></span> <span class="iconfont icon-send"></span>
@ -37,7 +37,7 @@
<span class="tool-name"> <span class="tool-name">
<span class="iconfont icon-tool"></span> <span class="iconfont icon-tool"></span>
{{ props.message.tool_calls[toolIndex].function.name }} {{ props.message.tool_calls[toolIndex].function!.name }}
</span> </span>
<el-button size="small" @click="createTest(props.message.tool_calls[toolIndex])"> <el-button size="small" @click="createTest(props.message.tool_calls[toolIndex])">
<span class="iconfont icon-send"></span> <span class="iconfont icon-send"></span>
@ -46,7 +46,7 @@
</div> </div>
<div class="tool-arguments"> <div class="tool-arguments">
<json-render :json="props.message.tool_calls[toolIndex].function.arguments"/> <json-render :json="props.message.tool_calls[toolIndex].function!.arguments"/>
</div> </div>
<!-- 工具调用结果 --> <!-- 工具调用结果 -->
@ -318,6 +318,9 @@ function updateToolCallResultItem(value: any, toolIndex: number, index: number)
.tool-call-header { .tool-call-header {
display: flex; display: flex;
align-items: center; align-items: center;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
} }
.tool-call-header.result { .tool-call-header.result {

Some files were not shown because too many files have changed in this diff Show More