feat: add DSH plugin marketplace and shared MCP
GoodBuddy could share Skills across runtimes, but custom MCP remained limited and DeepSeek Harness could not manage third-party extensions. The app now provides a default-off DSH npm marketplace with managed installation, configuration, failure isolation, and packaged npm support, while assigned custom MCP is available to managed OpenCode, Continue Agent, and DeepSeek Harness in Execute. Third-party DSH install scripts, initialization, and tools run with the current user's permissions. Ask remains read-only at dispatch, and turning off the marketplace hides management without disabling installed plugins. Release note: 新增默认关闭的 DSH 插件市场,并让自定义 MCP 可分配给 OpenCode、Continue 和 DeepSeek Harness;安装第三方插件前会明确提示当前用户权限边界。
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
||||
import { createHash, randomUUID } from 'node:crypto'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import {
|
||||
lstat,
|
||||
open,
|
||||
@@ -38,10 +38,14 @@ import {
|
||||
} from '../browser/browser-model-tools'
|
||||
import { BrowserStaleReferenceError } from '../browser/cdp-browser-driver'
|
||||
import type { KnowledgeMcpGateway } from './knowledge-mcp-gateway'
|
||||
import {
|
||||
createMcpToolName,
|
||||
isValidMcpToolName,
|
||||
normalizeMcpToolSchema
|
||||
} from './mcp-tool-utils'
|
||||
|
||||
const MAX_MODEL_TOOLS = 100
|
||||
const MAX_MCP_SERVERS = 16
|
||||
const MAX_TOOL_SCHEMA_BYTES = 32 * 1024
|
||||
const MAX_TOOL_RESULT_BYTES = 256 * 1024
|
||||
const MAX_READ_BYTES = 256 * 1024
|
||||
const MAX_WRITE_BYTES = 512 * 1024
|
||||
@@ -293,47 +297,6 @@ function boundedJson(value: unknown, errorMessage: string): string {
|
||||
return serialized
|
||||
}
|
||||
|
||||
function normalizeToolSchema(value: unknown): Record<string, unknown> {
|
||||
let serialized: string
|
||||
try {
|
||||
serialized = JSON.stringify(value)
|
||||
} catch (error) {
|
||||
throw new Error('MCP 工具参数结构无效', { cause: error })
|
||||
}
|
||||
if (
|
||||
!serialized ||
|
||||
Buffer.byteLength(serialized) > MAX_TOOL_SCHEMA_BYTES
|
||||
) {
|
||||
throw new Error('MCP 工具参数结构超过 32KB 安全限制')
|
||||
}
|
||||
const schema = JSON.parse(serialized) as unknown
|
||||
if (
|
||||
!schema ||
|
||||
typeof schema !== 'object' ||
|
||||
Array.isArray(schema) ||
|
||||
(schema as Record<string, unknown>).type !== 'object'
|
||||
) {
|
||||
throw new Error('MCP 工具参数必须使用 object JSON Schema')
|
||||
}
|
||||
return schema as Record<string, unknown>
|
||||
}
|
||||
|
||||
function createMcpToolName(serverId: string, originalName: string): string {
|
||||
const serverHash = createHash('sha256')
|
||||
.update(serverId)
|
||||
.digest('hex')
|
||||
.slice(0, 8)
|
||||
const toolHash = createHash('sha256')
|
||||
.update(originalName)
|
||||
.digest('hex')
|
||||
.slice(0, 8)
|
||||
const readable = originalName
|
||||
.replace(/[^a-zA-Z0-9_-]+/gu, '_')
|
||||
.replace(/^_+|_+$/gu, '')
|
||||
.slice(0, 36) || 'tool'
|
||||
return `mcp_${serverHash}_${toolHash}_${readable}`.slice(0, 64)
|
||||
}
|
||||
|
||||
function createTextToolResult(text: string): ModelToolResult {
|
||||
const contextBytes = Buffer.byteLength(text)
|
||||
if (contextBytes > MAX_TOOL_RESULT_BYTES) {
|
||||
@@ -852,7 +815,7 @@ export class ModelToolProvider implements ModelToolProviderLike {
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
.slice(0, 1_000),
|
||||
inputSchema: normalizeToolSchema(tool.inputSchema),
|
||||
inputSchema: normalizeMcpToolSchema(tool.inputSchema),
|
||||
source: 'mcp',
|
||||
serverName: server.name,
|
||||
taskSupport: tool.execution?.taskSupport
|
||||
@@ -860,13 +823,7 @@ export class ModelToolProvider implements ModelToolProviderLike {
|
||||
}))
|
||||
if (
|
||||
bindings.some(
|
||||
(tool) =>
|
||||
!tool.originalName ||
|
||||
tool.originalName.length > 128 ||
|
||||
[...tool.originalName].some((character) => {
|
||||
const code = character.charCodeAt(0)
|
||||
return code <= 31 || code === 127
|
||||
})
|
||||
(tool) => !isValidMcpToolName(tool.originalName)
|
||||
)
|
||||
) {
|
||||
throw new Error(`MCP Server「${server.name}」返回了无效工具名称`)
|
||||
|
||||
Reference in New Issue
Block a user