69 lines
2.0 KiB
TypeScript
69 lines
2.0 KiB
TypeScript
|
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
|
||
|
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
|
||
|
|
import { registerProcessCleanup, startCleanupTimer } from "./cleanup"
|
||
|
|
import { buildHttpRequestInit } from "./oauth-handler"
|
||
|
|
import type { ManagedClient, SkillMcpClientConnectionParams } from "./types"
|
||
|
|
|
||
|
|
export async function createHttpClient(params: SkillMcpClientConnectionParams): Promise<Client> {
|
||
|
|
const { state, clientKey, info, config } = params
|
||
|
|
|
||
|
|
if (!config.url) {
|
||
|
|
throw new Error(`MCP server "${info.serverName}" is configured for HTTP but missing 'url' field.`)
|
||
|
|
}
|
||
|
|
|
||
|
|
let url: URL
|
||
|
|
try {
|
||
|
|
url = new URL(config.url)
|
||
|
|
} catch {
|
||
|
|
throw new Error(
|
||
|
|
`MCP server "${info.serverName}" has invalid URL: ${config.url}\n\n` +
|
||
|
|
`Expected a valid URL like: https://mcp.example.com/mcp`
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
registerProcessCleanup(state)
|
||
|
|
|
||
|
|
const requestInit = await buildHttpRequestInit(config, state.authProviders)
|
||
|
|
const transport = new StreamableHTTPClientTransport(url, {
|
||
|
|
requestInit,
|
||
|
|
})
|
||
|
|
|
||
|
|
const client = new Client(
|
||
|
|
{ name: `skill-mcp-${info.skillName}-${info.serverName}`, version: "1.0.0" },
|
||
|
|
{ capabilities: {} }
|
||
|
|
)
|
||
|
|
|
||
|
|
try {
|
||
|
|
await client.connect(transport)
|
||
|
|
} catch (error) {
|
||
|
|
try {
|
||
|
|
await transport.close()
|
||
|
|
} catch {
|
||
|
|
// Transport may already be closed
|
||
|
|
}
|
||
|
|
|
||
|
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
||
|
|
throw new Error(
|
||
|
|
`Failed to connect to MCP server "${info.serverName}".\n\n` +
|
||
|
|
`URL: ${config.url}\n` +
|
||
|
|
`Reason: ${errorMessage}\n\n` +
|
||
|
|
`Hints:\n` +
|
||
|
|
` - Verify the URL is correct and the server is running\n` +
|
||
|
|
` - Check if authentication headers are required\n` +
|
||
|
|
` - Ensure the server supports MCP over HTTP`
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
const managedClient = {
|
||
|
|
client,
|
||
|
|
transport,
|
||
|
|
skillName: info.skillName,
|
||
|
|
lastUsedAt: Date.now(),
|
||
|
|
connectionType: "http",
|
||
|
|
} satisfies ManagedClient
|
||
|
|
|
||
|
|
state.clients.set(clientKey, managedClient)
|
||
|
|
startCleanupTimer(state)
|
||
|
|
return client
|
||
|
|
}
|