refactor(skill-mcp-manager): split manager.ts into connection and client modules

Extract MCP client lifecycle management:
- connection.ts: getOrCreateClientWithRetry logic
- stdio-client.ts, http-client.ts: transport-specific creation
- oauth-handler.ts: OAuth token management
- cleanup.ts: session and global cleanup
- connection-type.ts: connection type detection
This commit is contained in:
YeonGyu-Kim
2026-02-08 16:21:28 +09:00
parent 2beb29f326
commit 1bdfd409e9
8 changed files with 590 additions and 541 deletions
@@ -0,0 +1,68 @@
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
}