Skip to content
20 changes: 20 additions & 0 deletions src/main/presenter/configPresenter/mcpConfHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ const DEFAULT_INMEMORY_SERVERS: Record<string, MCPServerConfig> = {
},
disable: false
},
difyKnowledge: {
args: [],
descriptions: 'DeepChat内置Dify知识库检索服务',
icons: '📚',
autoApprove: ['all'],
type: 'inmemory' as MCPServerType,
command: 'difyKnowledge',
env: '{"configs":[{"description":"this is a description for the current knowledge base","apiKey":"YOUR_DIFY_API_KEY","datasetId":"YOUR_DATASET_ID","endpoint":"http://dify.y.sanrun.fun/v1"}]}',
disable: false
},
imageServer: {
args: [],
descriptions: 'Image processing MCP service',
Expand All @@ -82,6 +92,16 @@ const DEFAULT_INMEMORY_SERVERS: Record<string, MCPServerConfig> = {
command: 'powerpack',
env: {},
disable: false
},
ragflowKnowledge: {
args: [],
descriptions: 'DeepChat内置RAGFlow知识库检索服务',
icons: '📚',
autoApprove: ['all'],
type: 'inmemory' as MCPServerType,
command: 'ragflowKnowledge',
env: '{"configs":[{"description":"默认RAGFlow知识库","apiKey":"YOUR_RAGFLOW_API_KEY","datasetIds":["YOUR_DATASET_ID"],"endpoint":"http://localhost:8000"}]}',
disable: false
}
}

Expand Down
12 changes: 10 additions & 2 deletions src/main/presenter/deeplinkPresenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface MCPInstallConfig {
{
command?: string
args?: string[]
env?: Record<string, string>
env?: Record<string, string> | string
descriptions?: string
icons?: string
autoApprove?: string[]
Expand Down Expand Up @@ -250,7 +250,15 @@ export class DeeplinkPresenter implements IDeeplinkPresenter {

// Merge configuration
const finalConfig: MCPServerConfig = {
env: { ...defaultConfig.env, ...serverConfig.env },
env: {
...(typeof defaultConfig.env === 'string'
? JSON.parse(defaultConfig.env)
: defaultConfig.env),
...(typeof serverConfig.env === 'string'
? JSON.parse(serverConfig.env)
: serverConfig.env)
},
// env: { ...defaultConfig.env, ...serverConfig.env },
descriptions: serverConfig.descriptions || defaultConfig.descriptions!,
icons: serverConfig.icons || defaultConfig.icons!,
autoApprove: serverConfig.autoApprove || defaultConfig.autoApprove!,
Expand Down
12 changes: 9 additions & 3 deletions src/main/presenter/mcpPresenter/inMemoryServers/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,31 @@ import { BochaSearchServer } from './bochaSearchServer'
import { BraveSearchServer } from './braveSearchServer'
import { ImageServer } from './imageServer'
import { PowerpackServer } from './powerpackServer'
import { DifyKnowledgeServer } from './difyKnowledgeServer'
import { RagflowKnowledgeServer } from './ragflowKnowledgeServer'

export function getInMemoryServer(
serverName: string,
args: string[],
env?: Record<string, string>
env?: Record<string, string> | string
) {
switch (serverName) {
case 'buildInFileSystem':
return new FileSystemServer(args)
case 'Artifacts':
return new ArtifactsServer()
case 'bochaSearch':
return new BochaSearchServer(env)
return new BochaSearchServer(typeof env === 'object' ? env : undefined)
case 'braveSearch':
return new BraveSearchServer(env)
return new BraveSearchServer(typeof env === 'object' ? env : undefined)
case 'imageServer':
return new ImageServer(args[0], args[1])
case 'powerpack':
return new PowerpackServer()
case 'difyKnowledge':
return new DifyKnowledgeServer(typeof env === 'string' ? env : undefined)
case 'ragflowKnowledge':
return new RagflowKnowledgeServer(typeof env === 'string' ? env : undefined)
default:
throw new Error(`Unknown in-memory server: ${serverName}`)
}
Expand Down
284 changes: 284 additions & 0 deletions src/main/presenter/mcpPresenter/inMemoryServers/difyKnowledgeServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ToolSchema
} from '@modelcontextprotocol/sdk/types.js'
import { z } from 'zod'
import { zodToJsonSchema } from 'zod-to-json-schema'
import { Transport } from '@modelcontextprotocol/sdk/shared/transport'
import axios from 'axios'

// Schema definitions
const DifyKnowledgeSearchArgsSchema = z.object({
query: z.string().describe('搜索查询内容 (必填)'),
topK: z.number().optional().default(5).describe('返回结果数量 (默认5条)'),
scoreThreshold: z.number().optional().default(0.5).describe('相似度阈值 (0-1之间,默认0.5)')
})

const ToolInputSchema = ToolSchema.shape.inputSchema
type ToolInput = z.infer<typeof ToolInputSchema>

// 定义Dify API返回的数据结构
interface DifySearchResponse {
query: {
content: string
}
records: Array<{
segment: {
id: string
position: number
document_id: string
content: string
word_count: number
tokens: number
keywords: string[]
index_node_id: string
index_node_hash: string
hit_count: number
enabled: boolean
status: string
created_by: string
created_at: number
indexing_at: number
completed_at: number
document?: {
id: string
data_source_type: string
name: string
}
}
score: number
}>
}

// 导入MCPTextContent接口
import { MCPTextContent } from '@shared/presenter'

export class DifyKnowledgeServer {
private server: Server
private configs: Array<{
apiKey: string
endpoint: string
datasetId: string
description: string
}> = []

constructor(envStr?: string) {
if (!envStr) {
throw new Error('需要提供Dify知识库配置')
}

const envObj = JSON.parse(envStr)
const envs = envObj.configs

if (!Array.isArray(envs) || envs.length === 0) {
throw new Error('需要提供至少一个Dify知识库配置')
}

// 处理每个配置
for (const env of envs) {
if (!env.apiKey) {
throw new Error('需要提供Dify API Key')
}
if (!env.datasetId) {
throw new Error('需要提供Dify Dataset ID')
}
if (!env.description) {
throw new Error('需要提供对这个知识库的描述,以方便ai决定是否检索此知识库')
}

this.configs.push({
apiKey: env.apiKey,
datasetId: env.datasetId,
endpoint: env.endpoint || 'https://api.dify.ai/v1',
description: env.description
})
}

// 创建服务器实例
this.server = new Server(
{
name: 'deepchat-inmemory/dify-knowledge-server',
version: '0.1.0'
},
{
capabilities: {
tools: {}
}
}
)

// 设置请求处理器
this.setupRequestHandlers()
}

// 启动服务器
public startServer(transport: Transport): void {
this.server.connect(transport)
}

// 设置请求处理器
private setupRequestHandlers(): void {
// 设置工具列表处理器
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
const tools = this.configs.map((config, index) => {
const suffix = this.configs.length > 1 ? `_${index + 1}` : ''
return {
name: `dify_knowledge_search${suffix}`,
description: config.description,
inputSchema: zodToJsonSchema(DifyKnowledgeSearchArgsSchema) as ToolInput
}
})

return { tools }
})

// 设置工具调用处理器
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: parameters } = request.params

// 检查是否是Dify知识库搜索工具
if (name.startsWith('dify_knowledge_search')) {
try {
// 提取索引
let configIndex = 0
const match = name.match(/_([0-9]+)$/)
if (match) {
configIndex = parseInt(match[1], 10) - 1
}

// 确保索引有效
if (configIndex < 0 || configIndex >= this.configs.length) {
throw new Error(`无效的知识库索引: ${configIndex}`)
}

return await this.performDifyKnowledgeSearch(parameters, configIndex)
} catch (error) {
console.error('Dify知识库搜索失败:', error)
return {
content: [
{
type: 'text',
text: `搜索失败: ${error instanceof Error ? error.message : String(error)}`
}
]
}
}
}

return {
content: [
{
type: 'text',
text: `未知工具: ${name}`
}
]
}
})
}

// 执行Dify知识库搜索
private async performDifyKnowledgeSearch(
parameters: Record<string, unknown> | undefined,
configIndex: number = 0
): Promise<{ content: MCPTextContent[] }> {
const {
query,
topK = 5,
scoreThreshold = 0.5
} = parameters as {
query: string
topK?: number
scoreThreshold?: number
}

if (!query) {
throw new Error('查询内容不能为空')
}

// 获取当前配置
const config = this.configs[configIndex]

try {
const url = `${config.endpoint.replace(/\/$/, '')}/datasets/${config.datasetId}/retrieve`
console.log('performDifyKnowledgeSearch request', url, {
query,
retrieval_model: {
top_k: topK,
score_threshold: scoreThreshold
}
})

const response = await axios.post<DifySearchResponse>(
url,
{
query,
retrieval_model: {
top_k: topK,
score_threshold: scoreThreshold,
reranking_enable: null, // 下面这两个字段即使为空也必须要有,否则接口无法请求
score_threshold_enabled: null
}
},
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${config.apiKey}`
}
}
)

// 处理响应数据
const results = response.data.records.map((record) => {
const docName = record.segment.document?.name || '未知文档'
const docId = record.segment.document_id
const content = record.segment.content
const score = record.score

return {
title: docName,
documentId: docId,
content: content,
score: score,
keywords: record.segment.keywords || []
}
})

// 构建响应
let resultText = `### 查询: ${query}\n\n`

if (results.length === 0) {
resultText += '未找到相关结果。'
} else {
resultText += `找到 ${results.length} 条相关结果:\n\n`

results.forEach((result, index) => {
resultText += `#### ${index + 1}. ${result.title} (相关度: ${(result.score * 100).toFixed(2)}%)\n`
resultText += `${result.content}\n\n`

if (result.keywords && result.keywords.length > 0) {
resultText += `关键词: ${result.keywords.join(', ')}\n\n`
}
})
}

return {
content: [
{
type: 'text',
text: resultText
}
]
}
} catch (error) {
console.error('Dify API请求失败:', error)
if (axios.isAxiosError(error) && error.response) {
throw new Error(
`Dify API错误 (${error.response.status}): ${JSON.stringify(error.response.data)}`
)
}
throw error
}
}
}
Loading