Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -304,35 +304,24 @@ export class ModelscopeProvider extends OpenAICompatibleProvider {
]
const randomEmoji = emojis[Math.floor(Math.random() * emojis.length)]

// Get display name: chinese_name first, then id
const displayName = mcpServer.chinese_name || mcpServer.id
// Get display name: chinese_name first, then name, then id
const displayName = mcpServer.chinese_name || mcpServer.name || mcpServer.id

return {
name: `@modelscope/${mcpServer.id}`, // Use ModelScope ID format
description:
mcpServer.locales?.zh?.description ||
mcpServer.description ||
`ModelScope MCP Server: ${displayName}`,
command: '', // Not needed for SSE type
args: [], // Not needed for SSE type
env: {},
type: 'sse' as const, // SSE type for operational servers
baseUrl: baseUrl, // Use operational URL
enabled: false, // Default to disabled for safety
source: 'modelscope' as const,
// Additional metadata
descriptions:
mcpServer.locales?.zh?.description ||
mcpServer.description ||
`ModelScope MCP Server: ${displayName}`,
icons: randomEmoji, // Random emoji instead of URL
provider: 'ModelScope',
providerUrl: `https://www.modelscope.cn/mcp/servers/@${mcpServer.id}`,
logoUrl: '', // No longer using logo URL
tags: mcpServer.tags || [],
// Store original data for reference
originalId: mcpServer.id,
displayName: displayName
autoApprove: ['all'],
disable: false, // Default to disabled for safety
type: 'sse' as const, // SSE type for operational servers
baseUrl: baseUrl, // Use operational URL
source: 'modelscope',
sourceId: mcpServer.id
}
}
}
81 changes: 81 additions & 0 deletions src/main/presenter/mcpPresenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '@shared/presenter'
import { ServerManager } from './serverManager'
import { ToolManager } from './toolManager'
import { McpRouterManager } from './mcprouterManager'
import { eventBus, SendTarget } from '@/eventbus'
import { MCP_EVENTS, NOTIFICATION_EVENTS } from '@/events'
import { IConfigPresenter } from '@shared/presenter'
Expand Down Expand Up @@ -83,13 +84,21 @@ export class McpPresenter implements IMCPPresenter {
private toolManager: ToolManager
private configPresenter: IConfigPresenter
private isInitialized: boolean = false
// McpRouter
private mcprouter?: McpRouterManager

constructor(configPresenter?: IConfigPresenter) {
console.log('Initializing MCP Presenter')

this.configPresenter = configPresenter || presenter.configPresenter
this.serverManager = new ServerManager(this.configPresenter)
this.toolManager = new ToolManager(this.configPresenter, this.serverManager)
// init mcprouter manager
try {
this.mcprouter = new McpRouterManager(this.configPresenter)
} catch (e) {
console.warn('[MCP] McpRouterManager init failed:', e)
}

// 监听自定义提示词服务器检查事件
eventBus.on(CONFIG_EVENTS.CUSTOM_PROMPTS_SERVER_CHECK_REQUIRED, async () => {
Expand Down Expand Up @@ -181,6 +190,78 @@ export class McpPresenter implements IMCPPresenter {
}
}

// =============== McpRouter marketplace APIs ===============
async listMcpRouterServers(
page: number,
limit: number
): Promise<{
servers: Array<{
uuid: string
created_at: string
updated_at: string
name: string
author_name: string
title: string
description: string
content?: string
server_key: string
config_name?: string
server_url?: string
}>
}> {
if (!this.mcprouter) throw new Error('McpRouterManager not available')
const data = await this.mcprouter.listServers(page, limit)
return { servers: data && data.servers ? data.servers : [] }
}

async installMcpRouterServer(serverKey: string): Promise<boolean> {
if (!this.mcprouter) throw new Error('McpRouterManager not available')
return this.mcprouter.installServer(serverKey)
}

async getMcpRouterApiKey(): Promise<string> {
return this.configPresenter.getSetting<string>('mcprouterApiKey') || ''
}

async setMcpRouterApiKey(key: string): Promise<void> {
this.configPresenter.setSetting('mcprouterApiKey', key)
}

async isServerInstalled(source: string, sourceId: string): Promise<boolean> {
const servers = await this.configPresenter.getMcpServers()
for (const config of Object.values(servers)) {
if (config.source === source && config.sourceId === sourceId) {
return true
}
}
return false
}

async updateMcpRouterServersAuth(apiKey: string): Promise<void> {
const servers = await this.configPresenter.getMcpServers()
const updates: Array<{ name: string; config: Partial<MCPServerConfig> }> = []

for (const [serverName, config] of Object.entries(servers)) {
if (config.source === 'mcprouter' && config.customHeaders) {
const updatedHeaders = {
...config.customHeaders,
Authorization: `Bearer ${apiKey}`
}
updates.push({
name: serverName,
config: { customHeaders: updatedHeaders }
})
}
}

// 批量更新所有服务器的 Authorization
for (const update of updates) {
await this.configPresenter.updateMcpServer(update.name, update.config)
}

console.log(`Updated Authorization for ${updates.length} mcprouter servers`)
}

private scheduleBackgroundRegistryUpdate(): void {
setTimeout(async () => {
try {
Expand Down
124 changes: 124 additions & 0 deletions src/main/presenter/mcpPresenter/mcprouterManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { IConfigPresenter, MCPServerConfig } from '@shared/presenter'

type McpRouterListResponse = {
code: number
message: string
data?: {
servers: Array<{
uuid: string
created_at: string
updated_at: string
name: string
author_name: string
title: string
description: string
content?: string
server_key: string
config_name?: string
server_url?: string
}>
}
}

type McpRouterGetResponse = {
code: number
message: string
data?: {
created_at: string
updated_at: string
name: string
author_name: string
title: string
description: string
content?: string
server_key: string
config_name: string
server_url: string
}
}

const LIST_ENDPOINT = 'https://api.mcprouter.to/v1/list-servers'
const GET_ENDPOINT = 'https://api.mcprouter.to/v1/get-server'

export class McpRouterManager {
constructor(private readonly configPresenter: IConfigPresenter) {}

private getCommonHeaders(): Record<string, string> {
return {
'Content-Type': 'application/json',
'HTTP-Referer': 'deepchatai.cn',
'X-Title': 'DeepChat'
}
}

async listServers(page: number, limit: number): Promise<McpRouterListResponse['data']> {
const res = await fetch(LIST_ENDPOINT, {
method: 'POST',
headers: this.getCommonHeaders(),
body: JSON.stringify({ page, limit })
})
if (!res.ok) throw new Error(`McpRouter list failed: HTTP ${res.status}`)
const json = (await res.json()) as McpRouterListResponse
if (json.code !== 0) throw new Error(json.message || 'List servers error')
return json.data || { servers: [] }
}

async getServer(serverKey: string): Promise<McpRouterGetResponse['data']> {
const apiKey = this.configPresenter.getSetting<string>('mcprouterApiKey') || ''
if (!apiKey) throw new Error('McpRouter API key missing')
const headers = {
...this.getCommonHeaders(),
Authorization: `Bearer ${apiKey}`
}
const res = await fetch(GET_ENDPOINT, {
method: 'POST',
headers,
body: JSON.stringify({ server: serverKey })
})
if (!res.ok) throw new Error(`McpRouter get failed: HTTP ${res.status}`)
const json = (await res.json()) as McpRouterGetResponse
if (json.code !== 0 || !json.data) throw new Error(json.message || 'Get server error')
return json.data
}

private pickRandomEmoji(): string {
const emojis = ['🧩', '🛠️', '⚙️', '🚀', '🔧', '🧪', '📦', '🛰️', '🧠', '🔌', '📡', '🗂️']
const idx = Math.floor(Math.random() * emojis.length)
return emojis[idx]
}

/**
* Install a server from McpRouter to local MCP config as HTTP (Streamable) server
*/
async installServer(serverKey: string): Promise<boolean> {
const detail = await this.getServer(serverKey)
if (!detail) throw new Error('Server detail not found')

const apiKey = this.configPresenter.getSetting<string>('mcprouterApiKey') || ''
if (!apiKey) throw new Error('McpRouter API key missing')

// Build MCPServerConfig
const config: MCPServerConfig = {
command: '',
args: [],
env: {},
descriptions: detail.description || detail.title || detail.name,
icons: this.pickRandomEmoji(),
autoApprove: ['all'],
disable: false,
type: 'http',
baseUrl: detail.server_url,
customHeaders: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
'HTTP-Referer': 'deepchatai.cn',
'X-Title': 'DeepChat'
},
source: 'mcprouter',
sourceId: serverKey
}

const serverName = detail.config_name || detail.server_key || detail.name
return await this.configPresenter.addMcpServer(serverName, config)
}
}
Loading