feat: expand secure runtime and workspace UX
This commit is contained in:
@@ -1,79 +1,10 @@
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
||||
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
|
||||
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
||||
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
||||
import type {
|
||||
FetchLike,
|
||||
Transport
|
||||
} from '@modelcontextprotocol/sdk/shared/transport.js'
|
||||
import type { McpServerTestResult } from '../../shared/capability-contracts'
|
||||
import type { ResolvedMcpServer } from './capability-service'
|
||||
import { createMcpTransport } from './mcp-client-transport'
|
||||
|
||||
const MCP_TEST_TIMEOUT_MS = 12_000
|
||||
|
||||
function validateRemoteUrl(value: string): URL {
|
||||
const url = new URL(value)
|
||||
const hostname = url.hostname.toLowerCase().replace(/^\[|\]$/gu, '')
|
||||
if (
|
||||
hostname === '169.254.169.254' ||
|
||||
hostname === 'metadata.google.internal' ||
|
||||
hostname.endsWith('.internal.metadata')
|
||||
) {
|
||||
throw new Error('MCP 地址不能指向云平台元数据服务')
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
function createRestrictedFetch(origin: string): FetchLike {
|
||||
return async (input, init) => {
|
||||
const url = new URL(String(input))
|
||||
if (url.origin !== origin) {
|
||||
throw new Error('MCP Server 尝试访问未授权的跨域地址')
|
||||
}
|
||||
return fetch(url, {
|
||||
...init,
|
||||
redirect: 'error'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function createTransport(server: ResolvedMcpServer): Transport {
|
||||
if (server.transport === 'stdio') {
|
||||
return new StdioClientTransport({
|
||||
command: server.command,
|
||||
args: server.args,
|
||||
stderr: 'ignore',
|
||||
maxBufferSize: 2 * 1024 * 1024
|
||||
})
|
||||
}
|
||||
|
||||
const url = validateRemoteUrl(server.url)
|
||||
const requestInit: RequestInit | undefined = server.secret
|
||||
? {
|
||||
headers: {
|
||||
Authorization: `Bearer ${server.secret}`
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
const safeFetch = createRestrictedFetch(url.origin)
|
||||
|
||||
return server.transport === 'http'
|
||||
? new StreamableHTTPClientTransport(url, {
|
||||
fetch: safeFetch,
|
||||
requestInit,
|
||||
reconnectionOptions: {
|
||||
initialReconnectionDelay: 500,
|
||||
maxReconnectionDelay: 2_000,
|
||||
reconnectionDelayGrowFactor: 1.5,
|
||||
maxRetries: 0
|
||||
}
|
||||
})
|
||||
: new SSEClientTransport(url, {
|
||||
fetch: safeFetch,
|
||||
requestInit
|
||||
})
|
||||
}
|
||||
|
||||
export async function testMcpServer(
|
||||
server: ResolvedMcpServer
|
||||
): Promise<McpServerTestResult> {
|
||||
@@ -81,7 +12,7 @@ export async function testMcpServer(
|
||||
name: 'goodbuddy',
|
||||
version: '0.1.0'
|
||||
})
|
||||
const transport = createTransport(server)
|
||||
const transport = createMcpTransport(server)
|
||||
const controller = new AbortController()
|
||||
const timeout = setTimeout(() => {
|
||||
controller.abort(new Error('MCP 连接测试超时'))
|
||||
|
||||
Reference in New Issue
Block a user