mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-05 04:10:29 +08:00
feat: Add MCP server installation via URL protocol (#5351)
* Add MCP server installation via URL protocol Implement handler for cherrystudio://mcp/install URLs to add MCP servers from encoded configuration data. Supports multiple server configuration formats and adds a new IPC channel for server addition. * feat: Enhance MCP protocol handling and navigation - Implemented navigation to the '/settings/mcp' page using executeJavaScript in the MCP protocol URL handler. - Updated NavigationService to expose the navigate function globally for easier access in the application. - Added NavigateFunction type to the global environment for improved type safety in navigation operations. --------- Co-authored-by: kangfenmao <kangfenmao@qq.com>
This commit is contained in:
parent
f17fe3adb5
commit
0a4131379a
@ -38,6 +38,7 @@ export enum IpcChannel {
|
|||||||
MiniWindow_SetPin = 'miniwindow:set-pin',
|
MiniWindow_SetPin = 'miniwindow:set-pin',
|
||||||
|
|
||||||
// Mcp
|
// Mcp
|
||||||
|
Mcp_AddServer = 'mcp:add-server',
|
||||||
Mcp_RemoveServer = 'mcp:remove-server',
|
Mcp_RemoveServer = 'mcp:remove-server',
|
||||||
Mcp_RestartServer = 'mcp:restart-server',
|
Mcp_RestartServer = 'mcp:restart-server',
|
||||||
Mcp_StopServer = 'mcp:stop-server',
|
Mcp_StopServer = 'mcp:stop-server',
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { handleMcpProtocolUrl } from './urlschema/mcp-install'
|
||||||
import { windowService } from './WindowService'
|
import { windowService } from './WindowService'
|
||||||
|
|
||||||
export const CHERRY_STUDIO_PROTOCOL = 'cherrystudio'
|
export const CHERRY_STUDIO_PROTOCOL = 'cherrystudio'
|
||||||
@ -22,6 +23,12 @@ export function handleProtocolUrl(url: string) {
|
|||||||
const urlObj = new URL(url)
|
const urlObj = new URL(url)
|
||||||
const params = new URLSearchParams(urlObj.search)
|
const params = new URLSearchParams(urlObj.search)
|
||||||
|
|
||||||
|
switch (urlObj.hostname.toLowerCase()) {
|
||||||
|
case 'mcp':
|
||||||
|
handleMcpProtocolUrl(urlObj)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// You can send the data to your renderer process
|
// You can send the data to your renderer process
|
||||||
const mainWindow = windowService.getMainWindow()
|
const mainWindow = windowService.getMainWindow()
|
||||||
|
|
||||||
|
|||||||
76
src/main/services/urlschema/mcp-install.ts
Normal file
76
src/main/services/urlschema/mcp-install.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
|
import { IpcChannel } from '@shared/IpcChannel'
|
||||||
|
import { MCPServer } from '@types'
|
||||||
|
import Logger from 'electron-log'
|
||||||
|
|
||||||
|
import { windowService } from '../WindowService'
|
||||||
|
|
||||||
|
function installMCPServer(server: MCPServer) {
|
||||||
|
const mainWindow = windowService.getMainWindow()
|
||||||
|
|
||||||
|
if (!server.id) {
|
||||||
|
server.id = nanoid()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
mainWindow.webContents.send(IpcChannel.Mcp_AddServer, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function installMCPServers(servers: Record<string, MCPServer>) {
|
||||||
|
for (const name in servers) {
|
||||||
|
const server = servers[name]
|
||||||
|
if (!server.name) {
|
||||||
|
server.name = name
|
||||||
|
}
|
||||||
|
installMCPServer(server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleMcpProtocolUrl(url: URL) {
|
||||||
|
const params = new URLSearchParams(url.search)
|
||||||
|
switch (url.pathname) {
|
||||||
|
case '/install': {
|
||||||
|
// jsonConfig example:
|
||||||
|
// {
|
||||||
|
// "mcpServers": {
|
||||||
|
// "everything": {
|
||||||
|
// "command": "npx",
|
||||||
|
// "args": [
|
||||||
|
// "-y",
|
||||||
|
// "@modelcontextprotocol/server-everything"
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// cherrystudio://mcp/install?servers={base64Encode(JSON.stringify(jsonConfig))}
|
||||||
|
const data = params.get('servers')
|
||||||
|
if (data) {
|
||||||
|
const stringify = Buffer.from(data, 'base64').toString('utf8')
|
||||||
|
Logger.info('install MCP servers from urlschema: ', stringify)
|
||||||
|
const jsonConfig = JSON.parse(stringify)
|
||||||
|
Logger.info('install MCP servers from urlschema: ', jsonConfig)
|
||||||
|
|
||||||
|
// support both {mcpServers: [servers]}, [servers] and {server}
|
||||||
|
if (jsonConfig.mcpServers) {
|
||||||
|
installMCPServers(jsonConfig.mcpServers)
|
||||||
|
} else if (Array.isArray(jsonConfig)) {
|
||||||
|
for (const server of jsonConfig) {
|
||||||
|
installMCPServer(server)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
installMCPServer(jsonConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainWindow = windowService.getMainWindow()
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
mainWindow.webContents.executeJavaScript("window.navigate('/settings/mcp')")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
console.error(`Unknown MCP protocol URL: ${url}`)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/renderer/src/env.d.ts
vendored
2
src/renderer/src/env.d.ts
vendored
@ -3,6 +3,7 @@
|
|||||||
import type KeyvStorage from '@kangfenmao/keyv-storage'
|
import type KeyvStorage from '@kangfenmao/keyv-storage'
|
||||||
import { MessageInstance } from 'antd/es/message/interface'
|
import { MessageInstance } from 'antd/es/message/interface'
|
||||||
import { HookAPI } from 'antd/es/modal/useModal'
|
import { HookAPI } from 'antd/es/modal/useModal'
|
||||||
|
import { NavigateFunction } from 'react-router-dom'
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
VITE_RENDERER_INTEGRATED_MODEL: string
|
VITE_RENDERER_INTEGRATED_MODEL: string
|
||||||
@ -20,5 +21,6 @@ declare global {
|
|||||||
keyv: KeyvStorage
|
keyv: KeyvStorage
|
||||||
mermaid: any
|
mermaid: any
|
||||||
store: any
|
store: any
|
||||||
|
navigate: NavigateFunction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,9 @@ const ipcRenderer = window.electron.ipcRenderer
|
|||||||
ipcRenderer.on(IpcChannel.Mcp_ServersChanged, (_event, servers) => {
|
ipcRenderer.on(IpcChannel.Mcp_ServersChanged, (_event, servers) => {
|
||||||
store.dispatch(setMCPServers(servers))
|
store.dispatch(setMCPServers(servers))
|
||||||
})
|
})
|
||||||
|
ipcRenderer.on(IpcChannel.Mcp_AddServer, (_event, server: MCPServer) => {
|
||||||
|
store.dispatch(addMCPServer(server))
|
||||||
|
})
|
||||||
|
|
||||||
export const useMCPServers = () => {
|
export const useMCPServers = () => {
|
||||||
const mcpServers = useAppSelector((state) => state.mcp.servers)
|
const mcpServers = useAppSelector((state) => state.mcp.servers)
|
||||||
|
|||||||
@ -10,6 +10,7 @@ const NavigationService: INavigationService = {
|
|||||||
|
|
||||||
setNavigate: (navigateFunc: NavigateFunction): void => {
|
setNavigate: (navigateFunc: NavigateFunction): void => {
|
||||||
NavigationService.navigate = navigateFunc
|
NavigationService.navigate = navigateFunc
|
||||||
|
window.navigate = NavigationService.navigate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user